2013-02-19 22 views
5

tl; - W jaki sposób używać biblioteki Python-side, takiej jak PassLib, do mieszania haseł przed wstawieniem ich do MySQL DB z SQLAlchemy?SQLAlchemy & PassLib

porządku, więc już zostały pokoju moją głowę na moim biurku na dzień lub dwa próbuje dowiedzieć się tego, więc o to idzie:

Piszę aplikacji internetowych przy użyciu Pyramid i/SQLAlchemy Próbuję połączyć się z moją tabelą Użytkownicy bazy MySQL.

Docelowo chcę zrobić coś jak następuje:

Porównaj hasło hash:

if user1.password == 'supersecret' 

Włóż nowe hasło:

user2.password = 'supersecret' 

chciałbym aby móc używać PassLib do mieszania moich haseł zanim przejdą do bazy danych i nie jestem fanem używania wbudowanej funkcji SHA2 MySQL, ponieważ nie jest ona solona.

Jednak, żeby spróbować, mam tej pracy przy użyciu funkcji 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 

ten został skopiowany z przykładu 2 w http://www.sqlalchemy.org/trac/wiki/UsageRecipes/DatabaseCrypt

tak, że działa i pozwala mi korzystaj z wbudowanej funkcji SHA2 MySQL (dzwoniąc pod numer func.sha2()) i rób dokładnie to, co chcę. Jednak teraz próbuję zastąpić to przy pomocy PassLib po stronie Pythona.

PassLib prezentuje dwie funkcje: jeden na utworzenie nowego hasła hash, a jeden do zweryfikowania hasło:

from passlib.hash import sha256_crypt 

new_password = sha256_crypt.encrypt("supersecret") 

sha256_crypt.verify("supersecret", new_password) 

nie mogę dość dowiedzieć się, jak zaimplementować to w rzeczywistości. Po przeczytaniu całej dokumentacji uważam, że jest to albo inna forma TypeDecorator, deklaracja typu niestandardowego, wartość hybrydowa, albo właściwość hybrydowa. Próbowałem podążać za this, ale to naprawdę nie ma dla mnie sensu, ani też sugerowany tam kod faktycznie nie działa.

Podsumowując moje pytanie - w jaki sposób mogę przeciążać operatorów = i ==, aby uruchamiać operacje w odpowiednich funkcjach skrótu?

Odpowiedz

5

PasswordType z sqlalchemy-utils powinny być takie najlepiej pasuje do tego problemu. Używa passlib. Wycięte z docs:

Poniższy wykorzystanie stworzy kolumnę hasło, które będą automatycznie Hash nowych haseł pbkdf2_sha512 ale nadal porównanie haseł przeciwko uprzednio istniejących skrótów md5_crypt. W miarę porównywania haseł; skrót hasła w bazie danych zostanie zaktualizowany do pbkdf2_sha512.

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

Weryfikacja hasło jest tak proste, jak:

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

Dzięki! To jest nowe, ponieważ zadałem to pytanie, ale jest dokładnie tym, czego szukałem w tym czasie! – kc9jud

+0

Co powiesz na dodanie kolumny soli do tego modelu? Czy istnieje sposób na użycie soli z sqlalchemy-utils i przechowywanie? – steve

3

Jak rozumiem, co chcesz to:

  1. Szyfrowanie hasło użytkownika podczas tworzenia konta. Użyj sól i algorytm
  2. Kiedy użytkownik loguje się, hash hasła przy przychodzącego w ten sam sposób co robiłeś, gdy go przechowywać
  3. Porównaj dwa hashe za pomocą zwykłego porównania ciągów w swoim wniosku db

Więc, coś takiego jak dla kodu logowania:

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 byłby wynikiem zestawienia pasujących użytkowników (powinno być zero lub jeden oczywiście).

Zastrzeżenie - nie testowałem żadnej z tych

EDIT: Dziękuję za wskazanie, że PassLib wykorzystuje inną solą każdorazowo jest prowadzony. Najlepiej w tej sprawie, ponieważ nie wydaje się być prosty sposób to zrobić z SQLAlchemy, jest poniżej:

Ponadto, aby rozwiązać swój wniosek o abstrahując: wspólną metodologię do obsługi tego operacja polega na utworzeniu klasy "bezpieczeństwa" użyteczności dla uzyskania użytkownika (obiektu) zgodnego z przekazanymi poświadczeniami logowania.

Problem z bieżącą konfiguracją polega na tym, że konstruktor użytkownika ma dwa różne cele operacyjne, które, choć powiązane, niekoniecznie muszą być takie same: uwierzytelnianie użytkownika i uzyskiwanie obiektu użytkownika (na przykład lista użytkowników Grupa). Konstruktor staje się niepotrzebnie złożony w tym przypadku. Lepiej umieścić tę logikę, gdzie może być zamknięty z innego zabezpieczenia lub związanych login-funkcjonalności takich jak logowania użytkownika poprzez identyfikator sesji lub OSM żeton zamiast nazwy użytkownika/hasło:

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

Chyba tam dwa powody, to nie jest to, czego szukam. Po pierwsze, PassLib nigdy nie generuje tego samego ciągu hashowego hasła dwa razy z powodu solenia, więc nie można po prostu porównać ciągów. * Masz *, aby użyć funkcji verify(). Po drugie, próbuję wyodrębnić całą haszującą mechanikę/logikę na klasę, więc po prostu uruchom funkcję verify() na zmiennym haseł obiektu w tym celu. Dzięki za sugestię! – kc9jud

+0

Dzięki za informacje o PassLib. Trzeba było trochę kopać, aby znaleźć w dokumentacji, gdzie faktycznie to powiedział. Zaktualizowałem, aby rozwiązać te problemy. – Fyrilin

+0

Zgadzam się w 100% - Nie mam zamiaru weryfikować referencji ani autoryzacji w obiekcie Użytkownicy. Posiadanie pewnego rodzaju tokena jest jednak dobrym pomysłem, jednak chciałbym, aby klasa Użytkownicy pominęła specyfikę funkcji mieszającej. Chciałbym, aby mogłem zdecydować się na użycie innej funkcji hashowej udostępnionej w PassLib lub innej bibliotece i móc przełączać się, zmieniając tylko klasę Users - lub jeśli chciałem użyć hashowania PostgreSQL na PostgreSQL i PassLib na MySQL itp. Zasadniczo chcę ukryć fakt, że hasła są w ogóle mieszane. – kc9jud