2013-02-19 15 views
5

tl; dr - Come utilizzare una libreria lato Python come PassLib in password hash prima di inserirli in un DB MySQL con SQLAlchemy?SQLAlchemy & PassLib

Va bene, così ho sbattere la testa sulla mia scrivania per un giorno o due cercando di capire questo fuori, così qui va:

Sto scrivendo un'applicazione web utilizzando Piramide/SQLAlchemy e Sto cercando di interfacciarmi con la tabella Utenti del mio database MySQL.

In definitiva, voglio fare qualcosa di simile al seguente:

Confronta una password per l'hash:

if user1.password == 'supersecret' 

Inserire una nuova password:

user2.password = 'supersecret' 

mi piacerebbe essere in grado di usare PassLib per cancellare le mie password prima che entrino nel database, e non sono proprio un fan dell'uso della funzione incorporata di MySQL SHA2 poiché non è salata.

Tuttavia, solo per provare, io ho questo lavoro utilizzando la funzione SQL-side:

from sqlalchemy import func, TypeDecorator, type_coerce 
from sqlalchemy.dialects.mysql import CHAR, VARCHAR, INTEGER 
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy import Column 

class SHA2Password(TypeDecorator): 
    """Applies the SHA2 function to incoming passwords.""" 
    impl = CHAR(64) 

    def bind_expression(self, bindvalue): 
    return func.sha2(bindvalue, 256) 

    class comparator_factory(CHAR.comparator_factory): 
    def __eq__(self, other): 
     local_pw = type_coerce(self.expr, CHAR) 
     return local_pw == func.sha2(other, 256) 

class User(Base): 
    __tablename__ = 'Users' 
    _id = Column('userID', INTEGER(unsigned=True), primary_key=True) 
    username = Column(VARCHAR(length=64)) 
    password = Column(SHA2Password(length=64)) 

    def __init__(self, username, password): 
    self.username = username 
    self.password = password 

Questo è stato copiato dall'esempio 2 a http://www.sqlalchemy.org/trac/wiki/UsageRecipes/DatabaseCrypt

Così che funziona e mi permette di utilizzare la funzione incorporata di MySQL SHA2 (chiamando func.sha2()) e fare esattamente ciò che voglio. Tuttavia, ora sto cercando di rimpiazzarlo con PassLib sul lato Python.

PassLib presenta due funzioni: una di creare una nuova hash della password, e uno per verificare una password

from passlib.hash import sha256_crypt 

new_password = sha256_crypt.encrypt("supersecret") 

sha256_crypt.verify("supersecret", new_password) 

Non riesco a capire come implementare in realtà questo. Dopo aver letto tutta la documentazione, penso che sia una forma diversa di TypeDecorator, una dichiarazione di tipo personalizzato, un valore ibrido o una proprietà ibrida. Ho provato a seguire this, ma per me non ha alcun senso, né il codice suggerito in realtà viene eseguito.

Quindi, per riassumere la mia domanda: come faccio a sovraccaricare gli operatori = e == in modo che eseguano le operazioni tramite le funzioni di hash appropriate?

risposta

5

PasswordType da sqlalchemy-utils dovrebbe essere il si adatta meglio a questo problema. Usa passlib. Tagliò dalla documentazione:

Il seguente utilizzo creerà una colonna password che hash automaticamente una nuova password come pbkdf2_sha512 ma confrontare ancora le password hash contro md5_crypt pre-esistenti. Come le password vengono confrontate; l'hash della password nel database verrà aggiornato per essere pbkdf2_sha512.

class Model(Base): 
    password = sa.Column(PasswordType(
     schemes=[ 
      'pbkdf2_sha512', 
      'md5_crypt' 
     ], 
     deprecated=['md5_crypt'] 
    )) 

Verifica la password è facile come:

target = Model() 
target.password = 'b' 
# '$5$rounds=80000$H.............' 
target.password == 'b' 
# True 
+0

Grazie! Questo è nuovo poiché ho fatto questa domanda, ma è esattamente quello che stavo cercando in quel momento! – kc9jud

+0

Che ne dici di aggiungere una colonna di sale a questo modello? C'è un modo per usare un sale con sqlalchemy-utils e store? – steve

3

Da quanto ho capito, ciò che si vuole è questo:

  1. Crittografare la password dell'utente al momento della creazione dell'account. Usa il tuo sale e algoritmo
  2. Quando l'utente entra, hash della password in entrata nello stesso modo hai fatto quando hai memorizzato
  3. Confronta i due hash utilizzando regolare confronto tra stringhe nella richiesta db

Quindi, qualcosa di simile per il codice di accesso:

from passlib.hash import sha256_crypt 
passHash = sha256_crypt.encrypt(typed_password) 
// call your sqlalchemy code to query the db with this value (below) 

// In your SQLAlchemy code assuming "users" is your users table 
// and "password" is your password field 
s = users.select(and_(users.username == typed_username, users.password == passHash)) 
rs = s.execute() 

rs sarebbe il gruppo di risultati di utenti corrispondenti (dovrebbe essere zero o uno, ovviamente).

Disclaimer - non ho la prova di tutto questo

Edit: Grazie per aver ricordato che PassLib utilizza un diverso sale ogni volta che viene eseguito. La soluzione migliore in questo caso, dal momento che non sembra essere un modo semplice per farlo con SQLAlchemy, è il seguito:

Inoltre, per rispondere alla sua richiesta di astrazione: una metodologia comune per la gestione di questo operazione è creare una classe di utilità "sicurezza" per ottenere l'utente (oggetto) che corrisponde alle credenziali di accesso passate.

Il problema con la configurazione corrente è che il costruttore Utente ha due obiettivi operativi diversi che, sebbene correlati, non sono necessariamente la stessa cosa: autenticazione di un utente e acquisizione di un oggetto Utente (per esempio, un elenco di utenti in un gruppo). In questo caso, il costruttore diventa inutilmente complesso. E 'meglio mettere che la logica in cui può essere incapsulato con altro titolo o la funzionalità login correlati come ad esempio la registrazione in un utente tramite ID di sessione o token SSO al posto di username/password:

security.loginUser(username, password) 
# or security.loginUser(single_sign_on_token), etc. for polymorphic Security 
loggedInUser = security.getLoggedInUser() 

... later ... 
otherUser = User(username) #single job, simple, clean 
+0

Credo che ci sono due ragioni per cui questo non è quello che sto cercando. Per uno, PassLib non genera mai la stessa stringa di hash della password due volte in base alla salatura, quindi non puoi semplicemente confrontare le stringhe. È * necessario * utilizzare la funzione verify(). In secondo luogo, sto cercando di astrarre tutta la meccanica/logica di hashing nella classe, quindi basta eseguire verify() sulla variabile della password dell'oggetto tipo di sconfitte a tale scopo. Grazie per il suggerimento, però! – kc9jud

+0

Grazie per le informazioni su PassLib. Ci sono voluti alcuni scavi per trovare nella documentazione dove effettivamente detto. Ho aggiornato per indirizzare quelli. – Fyrilin

+0

Sono d'accordo al 100% - Non intendo verificare le credenziali o fare alcuna autenticazione nell'oggetto Users. Avere una sorta di token è sicuramente una buona idea, tuttavia, mi piacerebbe che la classe Users riassumesse le specifiche della funzione hash. Mi piacerebbe che potessi decidere di usare una diversa funzione di hash fornita in PassLib o in un'altra libreria ed essere in grado di cambiare cambiando solo la classe Users - o se volessi usare l'hashing PostgreSQL su PostgreSQL e PassLib su MySQL , ecc. Fondamentalmente, voglio nascondere il fatto che le password vengano sottoposte a hashing. – kc9jud