@@ -102,10 +102,11 @@ def from_wif(privkey):
102102 class Helper :
103103
104104 @staticmethod
105- def pinToAesKey (pin ):
105+ def pinToAesKey (pin , salt = "" , iterations = 2048 , hashfn = sha256 , phase1_key_length = 16 , phase2_key_length = 32 ):
106106 # use pbkdf2 magic
107- ret = pbkdf2 .pbkdf2 (pin , 16 )
108- ret = pbkdf2 .pbkdf2 (hexlify (ret ), 32 )
107+ # ( password, keylen, salt = "", itercount = 1024, hashfn = sha256 )
108+ ret = pbkdf2 .pbkdf2 (pin , phase1_key_length , salt , int (iterations / 2 ), hashfn )
109+ ret = pbkdf2 .pbkdf2 (hexlify (ret ), phase2_key_length , salt , int (iterations / 2 ), hashfn )
109110 return hexlify (ret ) # the encryption key
110111
111112 @staticmethod
@@ -115,7 +116,7 @@ def extractKey(encrypted_data, enc_key_hex):
115116 return BlockIo .Key .from_passphrase (unhexlify (decrypted ))
116117
117118 @staticmethod
118- def encrypt (data , key ):
119+ def encrypt (data , key , iv = None , cipher_type = "AES-256-ECB" , auth_data = None ):
119120 # key in hex, data as string
120121 # returns ciphertext in base64
121122
@@ -125,28 +126,69 @@ def encrypt(data, key):
125126 pad = lambda s : s + (BS - len (s ) % BS ) * chr (BS - len (s ) % BS )
126127 unpad = lambda s : s [0 :- s [- 1 ]]
127128
128- obj = AES .new (key , AES .MODE_ECB )
129- ciphertext = obj .encrypt (pad (data ).encode ('latin-1' ))
129+ obj = None
130130
131- return base64 .b64encode (ciphertext )
131+ if cipher_type == "AES-256-ECB" :
132+ obj = AES .new (key , AES .MODE_ECB )
133+ elif cipher_type == "AES-256-CBC" :
134+ obj = AES .new (key , AES .MODE_CBC , unhexlify (iv ))
135+ elif cipher_type == "AES-256-GCM" :
136+ obj = AES .new (key , AES .MODE_GCM , unhexlify (iv ))
137+ else :
138+ raise Exception ("Unsupported cipher" , cipher_type )
139+
140+ response = {"aes_iv" : iv , "aes_cipher_text" : None , "aes_auth_tag" : None , "aes_auth_data" : None , "aes_cipher" : cipher_type }
141+
142+ if cipher_type != "AES-256-GCM" :
143+ response ["aes_cipher_text" ] = base64 .b64encode (obj .encrypt (pad (data ).encode ('latin-1' )))
144+ else :
145+ # AES-256-GCM
146+ # no padding for data
147+ ciphertext = obj .encrypt (data .encode ('latin-1' ))
148+ response ["aes_cipher_text" ] = base64 .b64encode (ciphertext )
149+ response ["aes_auth_tag" ] = hexlify (obj .digest ())
150+
151+ return response
132152
133153 @staticmethod
134- def decrypt (b64data , key ):
154+ def decrypt (b64data , key , iv = None , cipher_type = "AES-256-ECB" , auth_tag = None ):
135155 # key in hex, b64data as base64 string
136156 # returns utf-8 string
137157
138158 message = None
139159
140160 try :
141- key = unhexlify (key ) # get bytes
142161
162+ key = unhexlify (key ) # get bytes
163+
143164 BS = 16
144165 pad = lambda s : s + (BS - len (s ) % BS ) * chr (BS - len (s ) % BS )
145166 unpad = lambda s : s [0 :- s [- 1 ]]
146-
167+
147168 data = base64 .b64decode (b64data ) # decode from base64
148- obj = AES .new (key , AES .MODE_ECB )
149- message = unpad (obj .decrypt (data ))
169+
170+ obj = None
171+
172+ if cipher_type == "AES-256-ECB" :
173+ obj = AES .new (key , AES .MODE_ECB )
174+ elif cipher_type == "AES-256-CBC" :
175+ obj = AES .new (key , AES .MODE_CBC , unhexlify (iv ))
176+ elif cipher_type == "AES-256-GCM" :
177+ obj = AES .new (key , AES .MODE_GCM , unhexlify (iv ))
178+ else :
179+ raise Exception ("Unsupported cipher" , cipher_type )
180+
181+ message = None
182+
183+ if cipher_type != "AES-256-GCM" :
184+ message = unpad (obj .decrypt (data ))
185+ else :
186+ # AES-256-GCM
187+ # Auth tag must be exactly 16 bytes
188+ if (len (auth_tag ) != 32 ):
189+ raise Exception ("Auth tag must be 16 bytes exactly." )
190+ message = obj .decrypt_and_verify (data , unhexlify (auth_tag ))
191+
150192 except :
151193 # error decrypting? must be an invalid secret pin
152194 raise Exception ('Invalid Secret PIN provided.' )
0 commit comments