Password leaked checker - Έλεγχος αν διέρευσε o κωδικός / συνθηματικό μας

Καλήσπέρα στην κοινότητα,
Αφού είδα το συγκεκριμένο βίντεο από το κανάλι computerphile στο yt : https://www.youtube.com/watch?v=hhUb5iknVJs
έγραψα το συγκεκριμένο πρόγραμμα σε python3 το οποίο ελέγχει τοπικά αν έχει “αλιευτεί” το password μας, με βάση την ιστοσελίδα have i been pwned(https://haveibeenpwned.com/). Το συγκεκριμένο πρόγραμμα κατεβάζει λίστα σε μορφή txt αρχείου και ελέγχει αν υπάρχει στην λίστα το password μας, χωρίς να υποβάλλει τον κωδικό στο site(γι’ αυτό αναφέρουμε την λέξη *τοπικά). Αντίστοιχο πρόγραμμα έφτιαξε και ο τύπος του βίντεο, το οποίο μπορούμε να βρούμε εδώ : https://github.com/mikepound/pwned-search.
Ο κώδικας του προγράμματος μου είναι ο εξής :

#!/usr/bin/env python
import hashlib
import requests
import urllib
password=str(raw_input('Give password : '))
hashedpass=hashlib.sha1(password.encode())
hexpass=hashedpass.hexdigest()
stripped=hexpass[0:5]
link='https://api.pwnedpasswords.com/range/'+stripped
list=requests.get(link)
f = open(“list.txt”,“w”)
f.write(list.text)
f.close
f = open(“list.txt”,“r”)
for line in f:
if hexpass[6:].upper() in line:
print line
f.close

Οποιεσδήποτε διορθώσεις ή απορίες είναι καλοδεχούμενες!

9 «Μου αρέσει»

Μπράβο!
Τις προάλλες, είχα μπει στο site, και λέω καλά…σε ρωτάει “τσέκαρε αν ο κωδικός σου είναι χακαρισμένος”, και εσύ πας και τον βάζεις;;; Κι αν αυτός που έχει κάνει το site κρατάει λεξικό με εκατομμύρια κωδικούς, και το δίνει όπου θέλει κατά το δοκούν, ποιος τον ελέγχει;…
Και σκεφτόμουν αυτή ακριβώς την ανάγκη, ο όποιος έλεγχος να γίνεται off-line!
Μπράβο και πάλι για την σκέψη, και την υλοποίηση!!!

6 «Μου αρέσει»

Εμένα πλέον στο ίντερνετ δεν μου φαίνεται τίποτα περίεργο. Δες εδώ κωδικό 12345 εν έτη 2019, 2.380.800 άτομα. Ο ανθρώπινος παράγοντας θα είναι πάντα μια τρύπα ασφαλείας ότι και να κάνεις.

3 «Μου αρέσει»

Ξεκινάμε με το προφανές, ότι το πρόγραμμα που έγραψες ΔΕΝ είναι python3 :D
Επίσης δεν έχεις σωστά τα indentations και δε δουλεύει ο κώδικας
Τέλος να βάζεις τον κώδικα σε code tags

Μια βελτιωμένη έκδοση με μήνυμα λάθους

#!/usr/bin/env python
import hashlib
import requests
import urllib

password=str(input('Give password : '))
hashedpass=hashlib.sha1(password.encode())
hexpass=hashedpass.hexdigest()
stripped=hexpass[0:5]

link='https://api.pwnedpasswords.com/range/'+stripped
list=requests.get(link)
f = open('list.txt','w')
f.write(list.text)
f.close
f = open('list.txt','r')

status="0"
for line in f:
    if hexpass[6:].upper() in line:
        status=line+"\nΟ Κωδικός έχει διαρεύσει"


if status=="0":
    print("Ο Κωδικός ΔΕΝ έχει διαρεύσει")

else:
    print(status)
        

Επίσης μια καλή προσθήκη θα ήταν να βγαίνει ο κωδικός που εισάγεται με αστεράκια για λόγους ασφαλείας.

2 «Μου αρέσει»

Ναι λάθος μου, είναι python2, λόγω βιασύνης.

Σε αυτό φταίει το γεγονός ότι το αντέγραψα από txt αρχείο για να το post-αρω και κατά την επικόλληση του στο blockquote χάθηκε το identation.

1 «Μου αρέσει»

Επίσης, αντί να βάζεις τον κώδικα σε code tags, τα οποία μάλλον εδώ δε διαθέτουν syntax highlighting, μπορείς να χρησιμοποιείς την ακόλουθη σύνταξη, ώστε να είναι πιο ευανάγνωστος ο κώδικας σου:

```<γλώσσα π.χ. python>
code here
```

Ας πούμε, για τον κώδικα του νήματος που έγραψε ο @Maras:

#!/usr/bin/env python
import hashlib
import requests
import urllib

password=str(input('Give password : '))
hashedpass=hashlib.sha1(password.encode())
hexpass=hashedpass.hexdigest()
stripped=hexpass[0:5]

link='https://api.pwnedpasswords.com/range/'+stripped
list=requests.get(link)
f = open('list.txt','w')
f.write(list.text)
f.close
f = open('list.txt','r')

status="0"
for line in f:
    if hexpass[6:].upper() in line:
        status=line+"\nΟ Κωδικός έχει διαρεύσει"


if status=="0":
    print("Ο Κωδικός ΔΕΝ έχει διαρεύσει")

else:
    print(status)

Δυστυχώς, o editor για τις απαντήσεις σε νήματα, τουλάχιστον για εμένα, δεν χρησιμοποιεί αυτή τη σύνταξη για τον κώδικα (αν και θα έπρεπε).
Το παραπάνω είναι μια απο τις δυνατότητες μίας γλώσσας, ιδιαίτερης εκφραστικότητας, η οποία ονομάζεται Markdown, και υποστηρίζεται σε κάποιον βαθμό από την πλατφόρμα που τρέχει το φορουμ.

2 «Μου αρέσει»

Μετά τις χρήσιμες προσθήκες και αναφορές του @billniakas και του @arvchristos, στο πρόγραμμα δεν φαίνεται η πληκτρολόγηση του κωδικού (μένει κενό το πεδίο, χωρίς βέβαια να φαίνεται πόσους χαρακτήρες πληκτρολογούμε) με την χρήση του getpass module.
Επίσης αν είναι να χρησιμοποιήσουμε ελληνικούς χαρακτήρες για τα μηνύματα θα πρέπει να δηλώσουμε και το αντίστοιχο encoding(utf-8 στην περίπτωση μας).

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import hashlib
import requests
import urllib
from getpass import getpass

password=getpass('Give password : ')
hashedpass=hashlib.sha1(password.encode())
hexpass=hashedpass.hexdigest()
stripped=hexpass[0:5]

link='https://api.pwnedpasswords.com/range/'+stripped
list=requests.get(link)
f = open('list.txt','w')
f.write(list.text)
f.close
f = open('list.txt','r')

status="0"
for line in f:
    if hexpass[6:].upper() in line:
        status=line+"\nΟ Κωδικός έχει διαρεύσει"


if status=="0":
    print("Ο Κωδικός ΔΕΝ έχει διαρεύσει")

else:
    print(status)

@billniakas θα το ψάξω αν μπορεί να βγάζει αστερίσκους :wink:

3 «Μου αρέσει»

Σκεφτόμουν να φτιάξω ένα ολόκληρο εργαλείο για αυτό και να το βάλω στο Google Home να μου λεει καθε μερα τι παίζει με τον λογαριασμό μου αλλά θέλουν 3.5 Ευρώ τον μήνα … https://haveibeenpwned.com/API/Key

Αν κατεβάζεις με κάποιον τρόπο κάθε n διάστημα και απλά ελέγχεις τους κωδικούς σου με κάποιο script δεν αφαιρείς την εξάρτηση απο το API ;

1 «Μου αρέσει»

Τους κωδικους μπορείς να τους ελέγχω χωρίς να χρειάζεται καποιο token. πχ έγγραψα το παρακάτω script σε Go να κάνει ακριβώς αυτό:

package main

import (
	"crypto/sha1"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"
)

// passToHash() encrypts your "password" with SHA-1 and returns it in hex
func passToHash(password string) string {
	hash := sha1.New()                     // Returns a new hash.Hash computing the SHA1 checksum
	hash.Write([]byte(password))           // `Write` expects bytes
	byteHash := hash.Sum(nil)              // This gets the finalized hash result as a byte slice uint8
	hexHash := fmt.Sprintf("%x", byteHash) // Use the `%x` format verb to convert a hash results to a hex string
	hexHash = strings.ToUpper(hexHash)     // Make the hash upper case
	return hexHash
}

// getPassList() fetches the API response and and error in case of a problem
// Read: https://haveibeenpwned.com/API/v3#SearchingPwnedPasswordsByRange
func getPassList(hash string) ([]string, error) {
	APIURL := fmt.Sprintf("%s%s", "https://api.pwnedpasswords.com/range/", hash)

	// Construct a *http.Request with a GET method against the APIURL
	req, err := http.NewRequest(http.MethodGet, APIURL, nil)
	if err != nil {
		return nil, err
	}

	// Create an HTTP Client to handle this *http.Request
	client := http.DefaultClient

	// Make the actual request using the client and get server's *http.Response
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	// Read only the body part of the *http.Response (as an array of bytes)
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	bodyStr := fmt.Sprintf("%#v", string(body)) // Convert bytes into string

	// Split the body string into sub-strings on every carriage return and newline character
	// creating an array of strings (password hashes)
	bodyStrArray := strings.Split(bodyStr, `\r\n`)
	return bodyStrArray, nil
}

// isPwned() checks if your "pass" is included in the "list" of pwned password
// and it returns how many times it has been hacked
func isPwned(list []string, pass string) (bool, string) {
	for _, value := range list {
		if strings.Contains(value, pass) {
			return true, value
		}
	}
	return false, ""
}

func main() {
	password := os.Args[1] // Use the first positional parameter as the password

	// Calculate the SHA-1 hash of your password
	hash := passToHash(password)

	// Query they API using the first 5 characters of it
	passList, err := getPassList(hash[0:5])
	if err != nil {
		log.Fatal(err)
	}

	// Test (locally) if your password is hacked by passing the rest of it
	pwned, result := isPwned(passList, hash[5:])
	if pwned {
		s := strings.Split(result, ":")
		times := s[1] // isolate the part after the ':'
		fmt.Printf("Your password have been PWNED %s times\n", times)
	} else {
		fmt.Printf("You are not PWNED\n")
	}
}

Output:

$ pwnedpass 123456
Your password have been PWNED 23547453 times

$ pwnedpass lkjdfuiusoiuf9ph34lksfsldf
You are not PWNED

Για τα υπόλοιπα API calls (πχ όπως αυτό που χρειάζονται e-mail address τα έχει κλειδωμένα. Για παράδειγμα, δοκιμαζοντας στον browser το e-mail μου, πετάει:

{ "statusCode": 401, "message": "Access denied due to missing hibp-api-key." }
2 «Μου αρέσει»

Μπράβο ρε παιδιά. Σε όλους.

Με χρήση της συνάρτησης readLineWithAsterisks(), παρμένη από εδώ : https://stackoverflow.com/questions/40651085/how-can-a-password-input-be-done-in-python-with-printing-an-asterisk-for-every-c
μπορούμε να εισάγουμε τον κωδικό, και να εμφανίζονται αστερίσκοι στη θέση των χαρακτήρων.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import hashlib
import requests
import urllib
import getch
import sys
from getpass import getpass

def readLineWithAsterisks():
    sBuffer = ''
    while True:
        c = getch.getch()
        if c == '\n':
            return sBuffer
        elif ord(c) == 127:
            if len(sBuffer) > 0:
                sys.stdout.write('\x08 \x08')
                sys.stdout.flush()
                sBuffer = sBuffer[0:-1]
            continue
        else:
            sys.stdout.write('*')
            sys.stdout.flush()
            sBuffer += c


print "Give password : "
password=readLineWithAsterisks()
hashedpass=hashlib.sha1(password.encode())
hexpass=hashedpass.hexdigest()
stripped=hexpass[0:5]

link='https://api.pwnedpasswords.com/range/'+stripped
list=requests.get(link)
f = open('list.txt','w')
f.write(list.text)
f.close
f = open('list.txt','r')

status="0"
for line in f:
    if hexpass[6:].upper() in line:
        status=line+"\nΟ Κωδικός έχει διαρεύσει"


if status=="0":
    print("Ο Κωδικός ΔΕΝ έχει διαρεύσει")

else:
    print(status)

@billniakas απλά με τους αστερίσκους, φαίνεται ο αριθμός των χαρακτήρων του κωδικού…(ίσως το να μην βγάζει τίποτα, όταν πληκτρολογούμε, είναι καλύτερη λύση).