I need to port this JavaScript code to Python:
var decrypt = function (text, password){
var decipher = crypto.createDecipher('aes-256-cbc',password);
var dec = decipher.update(text,'hex','utf8');
dec += decipher.final('utf8');
return dec;
}
The problem is that the PyCrypto library needs a key+IV and not just a password, and I don't know how to imitate the JavaScript crypto.createDecipher method.
Thanks a lot!
Ok, a possible solution is have a file decrypt.js with:
var crypto = require('crypto');
var decrypt = function (text, password){
var decipher = crypto.createDecipher('aes-256-cbc',password);
var dec = decipher.update(text,'hex','utf8');
dec += decipher.final('utf8');
return dec;
}
console.log(decrypt(process.argv[2], process.argv[3]));
And in your python file just use:
def decrypt(text, password):
p = subprocess.Popen(
['nodejs', 'decrypt.js', text, password],
stdout=subprocess.PIPE
)
stdoutdata, stderrdata = p.communicate()
return stdoutdata.decode('utf8')
Probably it's not the best option, but it works.
Now I have a complete Python 3 solution:
def decrypt(text, password):
key, iv = _evp_bytes_to_key(password, '', 32, 16)
decipher = AES.new(key, AES.MODE_CBC, iv)
return decipher.decrypt(bytes.fromhex(text.strip())).decode('utf8')
def _evp_bytes_to_key(password, salt, key_len, iv_len):
dtot = hashlib.md5((password + salt).encode()).digest()
d = [dtot]
while len(dtot) < (iv_len + key_len):
d.append(hashlib.md5(d[-1] + (password + salt).encode()).digest())
dtot = dtot + d[-1]
return dtot[:key_len], dtot[key_len:key_len+iv_len]
Related
I ran into a problem in translating code from PHP to Python.
Initially, there is a PHP code that creates a salted hash of a password with verification, and this code works fine and performs its functions.
But I had a need to transfer this code to python. However, the resulting final hash does not match the one obtained on PHP.
Help me please.
Here is the PHP code that works fine:
<?php
$username = 'test';
$password = '1234';
$salt = '5CD6A52E4F7046241C1607233395461D69D8C21709DD661FA1E9A24C8DF39647';
$g = gmp_init(7);
$N = gmp_init('894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7', 16);
$h1 = sha1(strtoupper($username . ':' . $password), TRUE);
$h2 = sha1($salt . $h1, TRUE);
$h2 = gmp_import($h2, 1, GMP_LSW_FIRST);
// g^h2 mod N
$verifier = gmp_powm($g, $h2, $N);
// convert back to a byte array (little-endian)
$verifier = gmp_export($verifier, 1, GMP_LSW_FIRST);
// pad to 32 bytes, remember that zeros go on the end in little-endian!
$verifier = str_pad($verifier, 32, chr(0), STR_PAD_RIGHT);
ECHO 'Verifier FINAL ', $verifier;
?>
Here's the python code I'm stuck on that doesn't produce the correct hash:
import hashlib
import secrets
import sys
USERNAME = 'test'
PASSWORD = '1234'
salt = '5CD6A52E4F7046241C1607233395461D69D8C21709DD661FA1E9A24C8DF39647'
g = 7
N = '894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7'
N = int('{0:08b}'.format(int(N, 16)), 2)
h1 = str(hashlib.sha1(str((USERNAME + ':' + PASSWORD).upper()).encode('utf-8')).hexdigest())
h2 = str(hashlib.sha1(str(salt + h1).encode('utf-8')).hexdigest())
h2 = int('{0:08b}'.format(int(h2, 16)), 2)
verifier = pow(g, h2, N)
verifier = format(verifier, "x").upper()
verifier = verifier.ljust(64, '0')
print('Verifier FINAL : ', verifier)
print('Verifier should be: E08847151054CB20CCD00A546A85D9A4E6EB882EDAB678DD8C68BB28DA22C678')
That's it, I managed to completely write a library for generating hashes and checking them.
import hashlib
import secrets
ACCOUNT_NAME = 'MyTestLogin'
ACCOUNT_PASSWORD = 'MyTestPassword'
def calculateSRP6Verifier(ACCOUNT_NAME: str, ACCOUNT_PASSWORD: str, SALT: str):
g = int(7)
N = int('894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7', 16)
h1 = bytes.fromhex(hashlib.sha1(((ACCOUNT_NAME + ':' + ACCOUNT_PASSWORD).upper()).encode()).hexdigest())
h2 = int(bytes.fromhex(hashlib.sha1(bytes.fromhex(SALT) + h1).hexdigest())[::-1].hex(), 16)
VERIFIER = bytes.fromhex(format(pow(g, h2, N), 'X').ljust(64, '0'))[::-1].hex().upper()
return VERIFIER
def getRegistrationData(ACCOUNT_NAME: str, ACCOUNT_PASSWORD: str):
SALT = secrets.token_hex(32)
VERIFIER = calculateSRP6Verifier(ACCOUNT_NAME, ACCOUNT_PASSWORD, SALT)
return SALT.upper(), VERIFIER
def verifySRP6(ACCOUNT_NAME: str, ACCOUNT_PASSWORD: str, SALT: str, VERIFIER: str):
g = int(7)
N = int('894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7', 16)
x = int(bytes.fromhex(hashlib.sha1(bytes.fromhex(SALT) + bytes.fromhex(hashlib.sha1(((ACCOUNT_NAME + ':' + ACCOUNT_PASSWORD).upper()).encode()).hexdigest())).hexdigest())[::-1].hex(), 16)
VERIFIER = bytes.fromhex(format(pow(g, x, N), "X").ljust(64, '0'))[::-1].hex().upper()
return VERIFIER
RESULT = getRegistrationData(ACCOUNT_NAME, ACCOUNT_PASSWORD)
print('This is SRP6 crypto library for Trinity Core 335a World of Warcraft server.')
print('')
print('Login : ' + ACCOUNT_NAME)
print('Password: ' + ACCOUNT_PASSWORD)
print('')
print('salt : ' + RESULT[0])
print('verifier: ' + RESULT[1])
# Note, that SALT and VERIFIER saved to the database as a binary string (not as hexadecimal, that returned from getRegistrationData() function)!
# Therefore, when inserting data into the MySQL database, you need to use such a query:
sql = "INSERT INTO account (username, salt, verifier, email) VALUES ('" + ACCOUNT_NAME.upper() + "', X'" + SALT + "', X'" + VERIFIER + "', '" + ACCOUNT_EMAIL.upper() + "')"
# This is simple example, how registration data is being verified.
#
#CODE - is return from verifySRP6() function
#VERIFIER and SALT - saved data from the MySQL database, from account table, converted from binary to hexadecimal string in upper() mode.
# MySQL query must be:
# cursor.execute("SELECT salt FROM account WHERE username = '" + ACCOUNT_NAME.upper() + "'")
# SALT = (cursor.fetchone()['salt']).hex().upper()
# cursor.execute("SELECT verifier FROM account WHERE username = '" + ACCOUNT_NAME.upper() + "'")
# VERIFIER = (cursor.fetchone()['verifier']).hex().upper()
#
#
#CODE = verifySRP6(ACCOUNT_NAME, ACCOUNT_PASSWORD, SALT, VERIFIER)
#print('check :', CODE)
#if(VERIFIER == CODE):
# print('Password is OK!')
#else:
# print('Wrong password!')
I use https://create.withcode.uk
So I found a AES (Advanced Encryption Standard) implementation in Python, but just the encoding and the decoding part:
def encrypt(string, password):
pad = lambda s : s + str.encode(chr(16 - len(s) % 16) * (16 - len(s) % 16))
password = str.encode(password)
string = str.encode(string)
salt = os.urandom(8) # Unpredictable enough for cryptographic use
salted = b''
dx = b''
while len(salted) < 48:
dx = md5_encode(dx + password + salt, True)
salted += dx
key = salted[0:32]
iv = salted[32:48]
cipher = AES.new(key, AES.MODE_CBC, iv)
encrypted_64 = base64.b64encode(cipher.encrypt(pad(string))).decode('ascii')
json_data = {
'ct': encrypted_64,
'iv': iv.hex(),
's': salt.hex()
}
return json.dumps(json_data, separators = (',', ':'))
Which works perfectly btw. Then after I did some "sentence = input('blah blah blah') and some password = input('blah blah'), after that, i did encrypt(sentence, password). But I'm really not that sure I did it correctly because it says : AttributeError: 'str' object has no attribute 'encode' on line 4
I'm new to Python.
str should be replaced by the string you want to encode, and the encoding you want to apply goes in the brackets.
password = password.encode(encoding='UTF-8')
string = string.encode(encoding='UTF-8')
I am trying to decrypt an AES CBC string for the number of times it was decrypted.
I was successful in decrypting the first time.
from Crypto.Cipher import AES
k = '57067125438768260656188878670043'
key = bytes(k, 'ascii')
i = '5706712543876826'
iv = bytes(i, 'ascii')
dec = AES.new(key, AES.MODE_CBC, iv)
n = 2
cipher = 'dd3364461dbca39ddb5eb32e9f11b81f000acac9ce8b91369f8bf7e4a88787785a8cc498c85ea20370e68f0e7014e92a2b5aedd4c670ec172d7adb45dfa5a770b582e8ed255bb857d94afdfd6e579525f24890070f984b8862133eda9cbb118ba7880db125c32dea7e7c54bc77abfc02'
def unpad(s):
return s[:-ord(s[len(s)-1:])]
def decrypt(cipherId):
cipher = bytes.fromhex(cipherId)
id = dec.decrypt(cipher)
node_dec = unpad(id.decode('utf-8'))
print (node_dec)
return node_dec
that is the first stage of encryption, but I don't know how to set a loop to run the function again based on n. this cipher for example was encrypted twice, it might be three or four or more.
I created another function to decrypt the output of the decrypt function because the output gives a different codec.
def decrypt_again(cipherId):
cipher = bytes.fromhex(cipherId)
id = dec.decrypt(cipher)
node_dec = unpad(id.decode('ISO-8859-1'))
print (node_dec)
return node_dec
so I tried writing this loop
x = decrypt(cipher)
for i in range (n):
y = decrypt_again(x)
but it only works for n = 1, if n is more than 1, it just keeps repeating y instead of parsing it again.
how can I go about it, please?
also if I re-run the function twice something happens to the string, I get ó³*Ä'»g)X»#¾ú84-8089-be57330fcd45 instead of this a214868d-f40b-4184-8089-be57330fcd45, there seem to be a break in the codec
any reason why this is so?
every time it sets y to decrypt_again(x), x is still the same so you should add a line that sets x to y so it remembers the result of the previous iteration
x = decrypt(cipher)
for i in range (n):
y = decrypt_again(x)
x = y
so guys I was able to solve the issue. here is the correct code that works top-notch.
from Crypto.Cipher import AES
k = '57067125438768260656188878670043'
key = bytes(k, 'ascii')
i = '5706712543876826'
iv = bytes(i, 'ascii')
n = 4
cipher = 'afc6f435d3bb68234bba2d39840d4c61cd0d45810940cf796ebf4078e1c0b2368caf618dd6c915fa3f9e0245372cc0dd9992d71aa3ac9e811a579a1d39fa9e26b0d1728d290f4bfa54931917afb45c49a815937498923e241a3eded50dff67ff18d9c1259404f1bdbafef7df5aba0c0b2aacc18079b6bb64058a7976246c0231b2178312c9a008dfa3bd954eef12c73d0b3adac8aa2f4733fe09767dc08741997a6243642d7a5fc627eede9fbeea9f9d931f90daa1cc6a760e352a36d55f22ea1a8bb4bd993d96f4cd006ad9de128d7ea9cd74b875ac2c55e62905b67eb354b5bac48961940fbdc1c0dc58ebc3b7356cb95c11505c8d9bbecc8670d7327ed5fcffd3577ed7d87adf628c5b44cb92c1e33f66715d2d429d3a62dc88a80fc3a3b4a720e9680b4ff444d4e87057a7fbe66afba6bffd96b641bfb03c0802528e30768c5695db92d0dd4f4db3d5301c17ed37accbe3c89d2c70d4fbb3e40d30164b640154fa9ecd5565b11af24f40fe91fbca3966ce1632a3dbbd6c1af432b2100a9f7136834fc00609170b6cf66a3861bf9aa1902292152ba6efdefeacc4d4d37e20896a42243f3ec2f4ae463cfa455e92d4df33688cb127ade64dbad056b1614fe2f2a32b0b2536a23f4b019511df378778c374413629e7afe83a047fe6745de19397193e7082f8ab27b4b41695f43abd9c'
def unpad(s):
return s[:-ord(s[len(s)-1:])]
def decrypt(cipherId):
dec = AES.new(key, AES.MODE_CBC, iv)
cipher = bytes.fromhex(cipherId)
id = dec.decrypt(cipher)
node_dec = unpad(id.decode('ISO-8859-1'))
return node_dec
if __name__ == '__main__':
x = decrypt(cipher)
result = ""
for i in range(n - 1):
y = decrypt(x)
x = y
result = x
print(result)
I have a code in scala where I have my encryption and decryption code, It works fine and the code is:
import java.util.Base64
import javax.crypto.Cipher
import javax.crypto.spec.{IvParameterSpec, SecretKeySpec}
class Encryption {
val key = "enIntVecTest2020"
val initVector = "encryptionIntVec"
def encrypt(text: String): String = {
val iv = new IvParameterSpec(initVector.getBytes("UTF-8"))
val skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES")
val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING")
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv)
val encrypted = cipher.doFinal(text.getBytes())
return Base64.getEncoder().encodeToString(encrypted)
}
def decrypt(text:String) :String={
val iv = new IvParameterSpec(initVector.getBytes("UTF-8"))
val skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES")
val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING")
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv)
val original = cipher.doFinal(Base64.getDecoder.decode(text))
new String(original)
}
}
val encryptobj = new Encryption()
val pwd = "#test#12345"
val result =encryptobj.encrypt(pwd)
pwd: String = #test#12345
result: String = lHhq1OzMSYnj+0XxiNzKhQ==
val pwd1 = encryptobj.decrypt(result)
println(pwd1)
pwd1: String = #test#12345
#test#12345
I tried in Python to achieve the same but does not give the expected encryption result, here is my code(I took the help of other similar answers):
from hashlib import sha256
import base64
from Crypto import Random
from Crypto.Cipher import AES
BS = 16
pad = lambda s: bytes(s + (BS - len(s) % BS) * chr(BS - len(s) % BS), 'utf-8')
unpad = lambda s : s[0:-ord(s[-1:])]
class AESCipher:
def __init__( self, key ):
self.key = bytes(key, 'utf-8')
def encrypt( self, raw ):
raw = pad(raw)
iv = "encryptionIntVec".encode('utf-8')
cipher = AES.new(self.key, AES.MODE_CBC, iv )
return base64.b64encode( iv + cipher.encrypt( raw ) )
def decrypt( self, enc ):
enc = base64.b64decode(enc)
iv = enc[:16]
cipher = AES.new(self.key, AES.MODE_CBC, iv )
return unpad(cipher.decrypt( enc[16:] )).decode('utf8')
cipher = AESCipher('enIntVecTest2020')
encrypted = cipher.encrypt('#test#12345')
decrypted = cipher.decrypt(encrypted)
print(encrypted)
b'ZW5jcnlwdGlvbkludFZlY5R4atTszEmJ4/tF8YjcyoU='
As you can see both the encryption is not right, I don't know where I am doing wrong. Please help in achieving the same encrypting result in python as showing in scala, I would be much thankful.
Thanks to #Topaco answer and after some search it worked.
import base64
from Crypto.Cipher import AES
BS = 16
pad = lambda s: bytes(s + (BS - len(s) % BS) * chr(BS - len(s) % BS), 'utf-8')
unpad = lambda s : s[0:-ord(s[-1:])]
class AESCipher:
def __init__( self, key ):
self.key = bytes(key, 'utf-8')
def encrypt( self, raw ):
raw = pad(raw)
iv = "encryptionIntVec".encode('utf-8')
cipher = AES.new(self.key, AES.MODE_CBC, iv )
return base64.b64encode(cipher.encrypt( raw ) )
def decrypt( self, enc ):
iv = "encryptionIntVec".encode('utf-8')
enc = base64.b64decode(enc)
cipher = AES.new(self.key, AES.MODE_CBC, iv )
return unpad(cipher.decrypt( enc )).decode('utf8')
cipher = AESCipher('enIntVecTest2020')
encrypted = cipher.encrypt('#test#12345')
print(encrypted.decode('utf-8'))
-> lHhq1OzMSYnj+0XxiNzKhQ==
decrypted = cipher.decrypt(encrypted)
print(decrypted)
-> #test#12345
I am trying to encrypt a UTF-8 string on Python with AES and decrypt it in Kotlin, here is the Python encrypt part:
class aes():
def __init__(self, key):
self.bs = AES.block_size
self.key = hashlib.sha256(key.encode()).digest()
def encrypt(self, raw):
raw = self.pad(raw)
iv = self.key[:self.bs] # Random.new().read(self.bs)
cipher = AES.new(self.key, AES.MODE_CBC, iv)
return base64.b64encode(iv + cipher.encrypt(raw.encode()))
def pad(self, s):
return s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs)
...
client_key = self.rsa.decrypt(int(connection.recv(bufsize).decode()))
enc = aes(client_key)
em = enc.encrypt("AES_OK")
connection.send(em+b'\0')
And the Kotlin part decrypting it:
object AES256 {
private val decorder = Base64.getDecoder()
private fun cipher(opmode:Int, secretKey:String):Cipher {
val c = Cipher.getInstance("AES/CBC/PKCS7Padding")
val sk = SecretKeySpec(secretKey.toByteArray(Charsets.UTF_8), "AES")
val iv = IvParameterSpec(secretKey.substring(0, 16).toByteArray(Charsets.UTF_8))
c.init(opmode, sk, iv)
return c
}
#RequiresApi(Build.VERSION_CODES.O)
fun decrypt(str:String, secretKey:String):String {
val byteStr = decorder.decode(str.toByteArray(Charsets.UTF_8))
// Error here
return String(cipher(Cipher.DECRYPT_MODE, secretKey).doFinal(byteStr))
}
}
fun readBytes(input: BufferedReader) : String {
var byte: Int = input.read()
var r = ""
while(byte != 0) {
r += byte.toChar()
byte = input.read()
}
return r
}
resposta = readBytes(input)
resposta = AES256.decrypt(resposta, atc_aesk.toString(Charsets.UTF_8))
And I get the following exception:
javax.crypto.BadPaddingException: error:1e000065:Cipher functions:OPENSSL_internal:BAD_DECRYPT
All the RSA and connection code works properly, the ciphertext and key are the same on both sides.
I'm using a 32 bytes key, I also tried a 16 bytes one, same error.
I would greatly appreciate any help or input, thanks.
The user President James K. Polk answered this in a comment, the mistake was that I hashed the key only in the Python code and not in Kotlin... self.key = hashlib.sha256(key.encode()).digest()
I tried finding the problem like a whole week, I really feel so dumb, thank you President James K. Polk.
I had exactly the same use case and seemed to have copied the same code from some places online. Still took me a second. Here is a working pair:
class AESCipher(object):
def __init__(self, key):
self.bs = AES.block_size
self.key = hashlib.sha256(key.encode()).digest()
def encrypt(self, raw):
raw = self._pad(raw)
iv = Random.new().read(AES.block_size)
cipher = AES.new(self.key, AES.MODE_CBC, iv)
return base64.b64encode(iv + cipher.encrypt(raw.encode())).decode('utf-8')
def _pad(self, s):
return s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs)
class AESCipher(private val key: String) {
private fun createCipher(mode: Int, ivBytes: ByteArray): Cipher {
val c = Cipher.getInstance("AES/CBC/PKCS7Padding")
val sk = SecretKeySpec(key.getSha256(), "AES")
val iv = IvParameterSpec(ivBytes)
c.init(Cipher.DECRYPT_MODE, sk, iv)
return c
}
fun decrypt(data: String): ByteArray {
val bytes = Base64.decode(data, Base64.DEFAULT)
val ivBytes = bytes.take(16).toByteArray()
val rawDataBytes = bytes.drop(16).toByteArray()
val cipher = createCipher(Cipher.DECRYPT_MODE, ivBytes)
return cipher.doFinal(rawDataBytes)
}
private fun String.getSha256(): ByteArray {
val digest = MessageDigest.getInstance("SHA-256").also { it.reset() }
return digest.digest(this.toByteArray())
}
}
If you pass the same key string to them, they will work together. Notice that the data exposed and consumed is already a Base64 string.