im trying to decrypt a chiphertext in PHP that was encrypted with AES-256-CCM using cryptography.hazmat in python
what i did in my python code is :
from cryptography.hazmat.primitives.ciphers.aead import AESCCM
from os import urandom
import base64
#Text To Encrypt
plaintext = bytes("message from python", encoding='utf-8')
#AES 256 Key Genrator
key = AESCCM.generate_key(256)
#Genrate Nonce
nonce= urandom(12)
#chipher
cipher = AESCCM(key, tag_length=8)
#Encryption
ciphertext = cipher.encrypt(nonce, plaintext, None)
then i convert the key , nonce and ciphertext to base64
key_b64 = base64.standard_b64encode(key)
ciphertext_b64 = base64.standard_b64encode(ciphertext)
nonce_b64 = base64.standard_b64encode(nonce)
in my example i got this results
key = b'\xcb\x14\x96{,0(\x15\x86 \xda\xf8\x1b"i|M\xbd\xc5d\xe7\xa6I\xdf\x7f\xe11\xae\xe8\x8a\xb3j'
key_b64 = b'yxSWeywwKBWGINr4GyJpfE29xWTnpknff+ExruiKs2o='
nonce = b'\xc7f\xdc\xe3\xe4\x03>M\x9by\x92\x9d
nonce_b64 = b'x2bc4+QDPk2beZKd'
ciphertext = b'R\x9f\xe6D\\_\xdexC\x82\xf8\x8e\x9b;\x91\xc7OLo\xc2\t/\x8fV>G='
ciphertext_b64 = b'Up/mRFxf3nhDgviOmzuRx09Mb8IJL49WPkc9'
i use the base64 results in my PHP code
<?php
$key_from_python = base64_decode('yxSWeywwKBWGINr4GyJpfE29xWTnpknff+ExruiKs2o=');
$ciphertext_from_python = base64_decode('ooGUzo0YiwKPs9+2wXySYEpdBNfSpyLUHm1M');
$nonce_from_python = base64_decode('Up/x2bc4+QDPk2beZKd');
$cipher = "aes-256-ccm";
if (in_array($cipher, openssl_get_cipher_methods())){
$ivlen = openssl_cipher_iv_length($cipher);
$iv = openssl_random_pseudo_bytes($ivlen);
$decrypted_mesage_from_pythom =
openssl_decrypt($encrypted_from_python_,$cipher,$key_from_python,$options=0 , $iv, $tag);
echo $decrypted_mesage_from_pythom;
}
its based on an example that i find here http://php.babo.ist/#/en/function.openssl-encrypt.html and i cant find another example
the decryption processes dose not return anything
and what really confusing me is :
we didn't use IV to encrypt in python code but the PHP need
non-NULL IV ,how to solve that ?
what $tag represent in PHP code and $tag_lenght both in PHP and
python(cipher = AESCCM(key, tag_length=8)) ?
if the decryption need nonce how to use it in my PHP code ?
How to get this work? encrypt from python and decrypt the same chiphertext in PHP
Note : i have to use python for encryption and php for decryption and i have to use AES-CCM, the python code is fixed , thank you for your understanding
thank you
There seems to be a bug in the PHP implementation for AES-CCM for a nonce length of 12 bytes (used by the OP) which results in a wrong ciphertext/tag. However, this bug is hidden by a number of flaws in the OP's PHP code. Therefore, these defects have to be fixed first:
The Python and PHP implementations differ in that in the Python code the ciphertext and tag are concatenated in that order, whereas in the PHP code the ciphertext and tag are processed separately.
The nonce in the Python code corresponds to the IV in the PHP code.
In the PHP code the OPENSSL_RAW_DATA flag must be set if the ciphertext is passed as raw data and not Base64 encoded.
The values for nonce and ciphertext (+ tag) differ in both codes. However, a correction is pointless because of the chosen 12 bytes nonce in combination with the PHP bug for a 12 bytes nonce, see below. I.e. a prerequisite for a successful test is a nonce size unequal to 12 bytes.
A PHP implementation that takes these points into account is e.g.
<?php
// Data from Python code
$key_from_python = base64_decode('<Base64 encoded key from Python>');
$ciphertext_from_python = base64_decode('<Base64 encoded (ciphertext + tag) from Python>');
$nonce_from_python = base64_decode('<Base64 encoded nonce from Python>');
$cipher = 'aes-256-ccm';
// Separate ciphertext and tag
$tagLength = 8;
$ciphertext = substr($ciphertext_from_python, 0, -$tagLength);
$tag = substr($ciphertext_from_python, -$tagLength);
// Decrypt
if (in_array($cipher, openssl_get_cipher_methods())){
$decrypted_mesage_from_pythom = openssl_decrypt($ciphertext, $cipher, $key_from_python, OPENSSL_RAW_DATA, $nonce_from_python, $tag);
echo $decrypted_mesage_from_pythom;
}
?>
With this PHP code it's possible to decrypt the data from the Python code as long as the length of the nonce is not equal to 12 bytes.
Python and PHP implementations allow a nonce with a length of 7 to 13 bytes (both inclusive), s. here for Python. Concerning the issue for a 12 bytes nonce, the following turns out: If in the PHP code the 12 bytes nonce is truncated to 7 bytes by removing the last 5 bytes, the same ciphertext/tag is created. The following PHP code illustrates this for the tag lengths of 8 and 16 bytes (PHP version 7.4.4):
<?php
function printCiphertextTag($plaintext, $key, $iv, $taglength){
$encrypted = openssl_encrypt($plaintext, "aes-256-ccm", $key, OPENSSL_RAW_DATA, $iv, $tag, NULL, $taglength);
echo sprintf("tag size: %2s, IV size: %2s, IV (hex): %-' 24s, ciphertext (hex): %s, tag (hex): %s\n", $taglength, strlen($iv), bin2hex($iv), bin2hex($encrypted), bin2hex($tag));
}
$plaintext = 'message from python';
$key = '01234567890123456789012345678901';
$nonce12 = openssl_random_pseudo_bytes(12);
$nonce7 = substr($nonce12, 0, 7);
printCiphertextTag($plaintext, $key, $iv = $nonce12, $taglength = 8);
printCiphertextTag($plaintext, $key, $iv = $nonce7, $taglength = 8);
printCiphertextTag($plaintext, $key, $iv = $nonce12, $taglength = 16);
printCiphertextTag($plaintext, $key, $iv = $nonce7, $taglength = 16);
?>
This result indicates a bug in the PHP implementation.
The Python code in contrast generates different ciphertexts/tags for a 12 bytes nonce compared to the PHP code (which is why the (corrected) OP's PHP code that uses a 12 bytes nonce fails). A check with Java/BC with identical parameters produces the same ciphertexts/tags for a 12 bytes nonce as the Python code, which verifies the values of the Python code and again indicates a bug in the PHP implementation.
EDIT: I've filed an issue here: https://bugs.php.net/bug.php?id=79601. Note: The issue was set to private by the admins, so that it cannot be opened (at least for now) without the appropriate permissions, s. here.
Surprisingly difficult to find a straight answer to this on Google.
I'm wanting to collect a piece of text and a message from a user such as 1PWP7a6xgoYx81VZocrDr5okEEcnqKkyDc hello world.
Then I want to be able to encrypt/decrypt the message with the text somehow so that I can save it in my database and not worry about the data being exposed if my website gets hacked,
encrypt('1PWP7a6xgoYx81VZocrDr5okEEcnqKkyDc', 'hello world')
decrypt('1PWP7a6xgoYx81VZocrDr5okEEcnqKkyDc', <encrypted_text>)
Is there a simple way to achieve this with python and please can someone provide/direct me to an example.
Perhaps an example of how to create public/private key pairs using a seed such as '1PWP7a6xgoYx81VZocrDr5okEEcnqKkyDc'?
Many thanks in advance :)
EDIT: Just to be clear I'm looking for a way to encrypt my users data in a determanistic way not obfuscate the message.
If that means I have to generate a PGP/GPG pub/pri key pair on the fly by using the text 1PWP7a6xgoYx81VZocrDr5okEEcnqKkyDc as a seed then that's fine but what's the method to do this?
Here's how to do it properly in CBC mode, including PKCS#7 padding:
import base64
from Crypto.Cipher import AES
from Crypto.Hash import SHA256
from Crypto import Random
def encrypt(key, source, encode=True):
key = SHA256.new(key).digest() # use SHA-256 over our key to get a proper-sized AES key
IV = Random.new().read(AES.block_size) # generate IV
encryptor = AES.new(key, AES.MODE_CBC, IV)
padding = AES.block_size - len(source) % AES.block_size # calculate needed padding
source += bytes([padding]) * padding # Python 2.x: source += chr(padding) * padding
data = IV + encryptor.encrypt(source) # store the IV at the beginning and encrypt
return base64.b64encode(data).decode("latin-1") if encode else data
def decrypt(key, source, decode=True):
if decode:
source = base64.b64decode(source.encode("latin-1"))
key = SHA256.new(key).digest() # use SHA-256 over our key to get a proper-sized AES key
IV = source[:AES.block_size] # extract the IV from the beginning
decryptor = AES.new(key, AES.MODE_CBC, IV)
data = decryptor.decrypt(source[AES.block_size:]) # decrypt
padding = data[-1] # pick the padding value from the end; Python 2.x: ord(data[-1])
if data[-padding:] != bytes([padding]) * padding: # Python 2.x: chr(padding) * padding
raise ValueError("Invalid padding...")
return data[:-padding] # remove the padding
It's set to work with bytes data, so if you want to encrypt strings or use string passwords make sure you encode() them with a proper codec before passing them to the methods. If you leave the encode parameter to True the encrypt() output will be base64 encoded string, and decrypt() source should be also base64 string.
Now if you test it as:
my_password = b"secret_AES_key_string_to_encrypt/decrypt_with"
my_data = b"input_string_to_encrypt/decrypt"
print("key: {}".format(my_password))
print("data: {}".format(my_data))
encrypted = encrypt(my_password, my_data)
print("\nenc: {}".format(encrypted))
decrypted = decrypt(my_password, encrypted)
print("dec: {}".format(decrypted))
print("\ndata match: {}".format(my_data == decrypted))
print("\nSecond round....")
encrypted = encrypt(my_password, my_data)
print("\nenc: {}".format(encrypted))
decrypted = decrypt(my_password, encrypted)
print("dec: {}".format(decrypted))
print("\ndata match: {}".format(my_data == decrypted))
your output would be similar to:
key: b'secret_AES_key_string_to_encrypt/decrypt_with'
data: b'input_string_to_encrypt/decrypt'
enc: 7roSO+P/4eYdyhCbZmraVfc305g5P8VhDBOUDGrXmHw8h5ISsS3aPTGfsTSqn9f5
dec: b'input_string_to_encrypt/decrypt'
data match: True
Second round....
enc: BQm8FeoPx1H+bztlZJYZH9foI+IKAorCXRsMjbiYQkqLWbGU3NU50OsR+L9Nuqm6
dec: b'input_string_to_encrypt/decrypt'
data match: True
Proving that same key and same data still produce different ciphertext each time.
Now, this is much better than ECB but... if you're going to use this for communication - don't! This is more to explain how it should be constructed, not really to be used in a production environment and especially not for communication as its missing a crucial ingredient - message authentication. Feel free to play with it, but you should not roll your own crypto, there are well vetted protocols that will help you avoid the common pitfalls and you should use those.
Based on zwer's answers but shows an example attempt to deal with the case where the source text is exactly a multiple of 16 (AES.block_size). However #zwer explains in a comment how this code will BREAK THE ENCRYPTION of your text by not padding your source text appropriately, making your pipeline insecure.
Code:
from builtins import bytes
import base64
from Crypto.Cipher import AES
from Crypto.Hash import SHA256
from Crypto import Random
def encrypt(string, password):
"""
It returns an encrypted string which can be decrypted just by the
password.
"""
key = password_to_key(password)
IV = make_initialization_vector()
encryptor = AES.new(key, AES.MODE_CBC, IV)
# store the IV at the beginning and encrypt
return IV + encryptor.encrypt(pad_string(string))
def decrypt(string, password):
key = password_to_key(password)
# extract the IV from the beginning
IV = string[:AES.block_size]
decryptor = AES.new(key, AES.MODE_CBC, IV)
string = decryptor.decrypt(string[AES.block_size:])
return unpad_string(string)
def password_to_key(password):
"""
Use SHA-256 over our password to get a proper-sized AES key.
This hashes our password into a 256 bit string.
"""
return SHA256.new(password).digest()
def make_initialization_vector():
"""
An initialization vector (IV) is a fixed-size input to a cryptographic
primitive that is typically required to be random or pseudorandom.
Randomization is crucial for encryption schemes to achieve semantic
security, a property whereby repeated usage of the scheme under the
same key does not allow an attacker to infer relationships
between segments of the encrypted message.
"""
return Random.new().read(AES.block_size)
def pad_string(string, chunk_size=AES.block_size):
"""
Pad string the peculirarity that uses the first byte
is used to store how much padding is applied
"""
assert chunk_size <= 256, 'We are using one byte to represent padding'
to_pad = (chunk_size - (len(string) + 1)) % chunk_size
return bytes([to_pad]) + string + bytes([0] * to_pad)
def unpad_string(string):
to_pad = string[0]
return string[1:-to_pad]
def encode(string):
"""
Base64 encoding schemes are commonly used when there is a need to encode
binary data that needs be stored and transferred over media that are
designed to deal with textual data.
This is to ensure that the data remains intact without
modification during transport.
"""
return base64.b64encode(string).decode("latin-1")
def decode(string):
return base64.b64decode(string.encode("latin-1"))
Tests:
def random_text(length):
def rand_lower():
return chr(randint(ord('a'), ord('z')))
string = ''.join([rand_lower() for _ in range(length)])
return bytes(string, encoding='utf-8')
def test_encoding():
string = random_text(100)
assert encode(string) != string
assert decode(encode(string)) == string
def test_padding():
assert len(pad_string(random_text(14))) == 16
assert len(pad_string(random_text(15))) == 16
assert len(pad_string(random_text(16))) == 32
def test_encryption():
string = random_text(100)
password = random_text(20)
assert encrypt(string, password) != string
assert decrypt(encrypt(string, password), password) == string
If you are going to use mentioned database to authorise users, you should use hashes or message digests of user's passwords, instead of 2 way encryption algorithms, that would make your data hard to use even in case of db leakage.
You cannot use above method to protect data that needs to be decrypted at some point, but even then you can use more secure way than just encrypting user passwords using some fixed key (which is the worst method). Take a look at OWASP's Password Storage Cheat Sheet.
As you wrote "I want to be able to encrypt/decrypt the message", I'm attaching a simple python source (tested under 2.7) for encr/decr using Blowfish.
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
import os
from Crypto.Cipher import Blowfish # pip install pycrypto
BS = 8
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s : s[0:-ord(s[-1])]
def doEncrypt(phrase, key):
c1 = Blowfish.new(key, Blowfish.MODE_ECB)
return c1.encrypt(pad(phrase))
def doDecrypt(phrase, key):
c1 = Blowfish.new(key, Blowfish.MODE_ECB)
return unpad(c1.decrypt(phrase))
def testing123(phrase, key):
encrypted = doEncrypt(phrase, key)
decrypted = doDecrypt(encrypted, key)
assert phrase == decrypted, "Blowfish ECB enc/dec verification failed"
print ("Blowfish ECB enc/dec verified ok")
print ('phrase/key(hex)/enc+dec: {}/{}/{}'.format(phrase, key.encode('hex'), decrypted))
if __name__== "__main__":
phrase= 'Ala ma kota, a kot ma AIDS.'
key= os.urandom(32)
testing123(phrase, key)
You can do it by using two of the built-in functions on the standard Python library. The first one is the function ord( ), which takes a unicode string character as single input parameter and converts it to its corresponding unicode code (an integer). Two simple examples of the usage of this function are provided:
>>> ord('a')
97
>>> ord('b')
98
Then, you also have the inverse function of ord(): chr( ). And as you can imagine it works all the way around: it has a unicode code as an input (integer) and gets the corresponding unicode character (string):
>>> chr(97)
'a'
>>> chr(98)
'b'
Then you can do a simple encription by adding or substracting by some arbitrary integer... in this case, the number 2:
NOTE: Watch out in not utting very big values or you'll get an error id you reach a negative nber, for example.
def encrypt(message):
newS=''
for car in message:
newS=newS+chr(ord(car)+2)
return newS
print(encrypt('hello world'))
And getting as a result:
jgnnq"yqtnf
Now you can copy and past the same function and generate the decrypt function. In this case, it requires, obviously, to substract by 2:
def decrypt(message):
newS=''
for car in message:
newS=newS+chr(ord(car)-2)
return newS
print(decrypt('jgnnq"yqtnf'))
And the result will be the original message again:
'hello world'
This would be a great way to encrypt messages to non programmers. However, anyone with a little of programming knowledge could write a program that varied the integer we used until they found we have just added (2) to the unicode characters to encrypt the code...
In order to avoid that, I would propose two more complex alternatives.
1. The first one is the simplest: it consists in applying a different sum value to the chr function depending on the position of the character (for example, adding 2 to each unicode code when it occupies an even position in the string and substracting 3 when sits on an odd position).
2. The second one will generate the maximum security. It will consist on adding or substracting every unicode code for a number that will be randomly generated for each character. It will require to store an array of values to decript back the message. Make sure, then, this array of values is not available to third parties.
There it goes a possible solution for 1.:
def encryptHard(message):
newS=''
for i in range(len(message)):
if i%2==0:
newS=newS+chr(ord(message[i])+2)
else:
newS=newS+chr(ord(message[i])-3)
return newS
print(encryptHard('hello world'))
And the result would be:
jbniqyltif
With the information hereby privided the decrypting script is obvious, so I won't bother you with coping, pasing and changing two values.
Finally, let's go into an in-depth-analysis of the second more complex alternative. With this one we can say that the encription will be almost indefitable. The idea is to vary the value we add or substract to each unicode code by a random number comprized between 0 and 255 (this is the range of numbers the chr( ) function admits, so do not try to play with other numbers o you will definitely get an error).
In this case, my proposal also randomizes the operation (sum or subtract), and avoids that the final number be a 0 (i.e. we would get an original character). Finally, its also returns a list with the numers it has been subtracted to, something you will need in order to decrypt the message back.
The chances that you get the same encrypted message if you call two times this function using the same message of length n are somewhat near to 255^n... So don't worry (I say somewhat, as the algorithm created would actually generate more repeated values on the low-end or high-end range of values, for example, in case the most frequent characters were not centered in this distrubution unicode caracrer set (from 0 to 255), which is the case. However, the program, though not perfect, works flawlessly and protects the information.
import random as r
def encryptSuperHard(message):
newS=''
l_trans=[]
for car in message:
code=ord(car)
add_subtract=r.choice([True,False])
if add_subtract:
transpose=r.randint(0,code-1)
newS=newS+chr(code-transpose)
l_trans=l_trans+[-transpose]
else:
transpose=r.randint(code+1,255)
newS=newS+chr(code+transpose)
l_trans=l_trans+[transpose]
return newS, l_trans
print(encryptSuperHard('hello world'))
In this case, this random encrypting script I've made has returned this two value tuple, where the first value is the encrypted message and the second one is the value that has "transposed" every character in order of apearance.
('A0ŤłY\x10řG;,à', [-39, -53, 248, 214, -22, -16, 226, -40, -55, -64, 124])
Decrypting, in this case would need to take the encrypred message and the list and proceed as follows:
def decryptSuperHard(encriptedS,l):
newS=''
for i in range(len(l)):
newS=newS+chr(ord(encriptedS[i])-l[i])
return newS
print(decryptSuperHard('A0ŤłY\x10řG;,à', [-39,-53,248,214,-22,-16,226,-40,-55,-64,124]))
And the results goes back to:
hello world
print(deccryptSuperHard('A0ŤłY\x10řG;,à', [-39, -53, 248, 214, -22, -16, 226, -40, -55, -64, 124])
Have gou considered using the cryptography package? Here’s a simple example using Fernet encryption from their README:
from cryptography.fernet import Fernet
key = Fernet.generate_key()
f = Fernet(key)
token = f.encrypt(b"A secret message")
f.decrypt(token)
Based on this answer, the following AES256-GCM solution is even safer, although it requires a nonce:
import secrets
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
# Generate a random secret key (AES256 needs 32 bytes)
key = secrets.token_bytes(32)
# Encrypt a message
nonce = secrets.token_bytes(12) # GCM mode needs 12 fresh bytes every time
ciphertext = nonce + AESGCM(key).encrypt(nonce, b"Message", b"")
# Decrypt (raises InvalidTag if using wrong key or corrupted ciphertext)
msg = AESGCM(key).decrypt(ciphertext[:12], ciphertext[12:], b"")
To install the cryptography package:
pip install cryptography
Cheers,
Cocco
Here is my solution for anyone who may be interested:
from Crypto.Cipher import AES # pip install pycrypto
import base64
def cypher_aes(secret_key, msg_text, encrypt=True):
# an AES key must be either 16, 24, or 32 bytes long
# in this case we make sure the key is 32 bytes long by adding padding and/or slicing if necessary
remainder = len(secret_key) % 16
modified_key = secret_key.ljust(len(secret_key) + (16 - remainder))[:32]
print(modified_key)
# input strings must be a multiple of 16 in length
# we achieve this by adding padding if necessary
remainder = len(msg_text) % 16
modified_text = msg_text.ljust(len(msg_text) + (16 - remainder))
print(modified_text)
cipher = AES.new(modified_key, AES.MODE_ECB) # use of ECB mode in enterprise environments is very much frowned upon
if encrypt:
return base64.b64encode(cipher.encrypt(modified_text)).strip()
return cipher.decrypt(base64.b64decode(modified_text)).strip()
encrypted = cypher_aes(b'secret_AES_key_string_to_encrypt/decrypt_with', b'input_string_to_encrypt/decrypt', encrypt=True)
print(encrypted)
print()
print(cypher_aes(b'secret_AES_key_string_to_encrypt/decrypt_with', encrypted, encrypt=False))
Result:
b'secret_AES_key_string_to_encrypt'
b'input_string_to_encrypt/decrypt '
b'+IFU4e4rFWEkUlOU6sd+y8JKyyRdRbPoT/FvDBCFeuY='
b'secret_AES_key_string_to_encrypt'
b'+IFU4e4rFWEkUlOU6sd+y8JKyyRdRbPoT/FvDBCFeuY= '
b'input_string_to_encrypt/decrypt'
I am trying to pass data between Python and Node.js application. For that i am using AES encryption. The problem is that Node.js produces encrypted data which is twice longer than the one produced using Python.
Below are code snippets.
Python 3.6
import binascii
from Crypto.Cipher import AES
key = 'key-xxxxxxxxxxxxxxxxxxZZ'
iv = '1234567812345678'
data = 'some_secret_data'
def _encrypt(data):
aes = AES.new(key, AES.MODE_CBC, iv[:16])
encrypted = aes.encrypt(data)
# encrypted = b'\xd54\xbb\x96\xd3\xbet#\x10\x01 [\reg\xaa'
encrypted_base64 = binascii.b2a_base64(encrypted)
# encrypted_base64 = b'1TS7ltO+dEAQASBbDWVnqg==\n'
encrypted_hex = binascii.hexlify(encrypted)
# encrypted_hex = b'd534bb96d3be74401001205b0d6567aa'
return encrypted_base64
output = _encrypt(data)
Node v6.10.0
let crypto = require("crypto");
let enc = require("./encryption");
var key = 'key-xxxxxxxxxxxxxxxxxxZZ';
var iv = '1234567812345678';
var data = 'some_secret_data';
var encrypted_hex = encrypt(data, 'hex');
var encrypted_base64 = encrypt(data, 'base64');
console.log(encrypted_hex);
// encrypted_hex = 'd534bb96d3be74401001205b0d6567aab4c31f7a76936598e5a1cc05385f3a91'
console.log(encrypted_base64);
// encrypted_base64 = '1TS7ltO+dEAQASBbDWVnqrTDH3p2k2WY5aHMBThfOpE='
function encrypt(msg, encoding){
var aes = crypto.createCipheriv('aes-192-cbc', key, iv);
var crypted = aes.update(msg,'utf8', encoding)
crypted += aes.final(encoding);
return crypted;
}
As you can see above, Python produces encrypted_hex which equals to d534bb96d3be74401001205b0d6567aa. In Node, encrypted_hex contains the value mentioned above + b4c31f7a76936598e5a1cc05385f3a91.
Could anyone help me understand what is going on here:
Why does Node.js produces result which is twice longer ?
I figured it out.
This happens because of different behavior of Pythons' PyCrypto package and Nodes' Crypto module. I am using AES, and since it is a block cypher, it requires data to come in chunks of specified length.
PyCrypto fails if it encounters data which doesn't come in 16 bit chunks. Nodes' Crypto, by default will pad data, so data will always come in chunks of good size.
For the sake of simplicity i've tested data having length of 16 bytes in the examples above. So why were the results different?
Python module did not pad data by default, and because data contained correct length, we've got expected result.
Node Crypto module, however, does pad the data, and apparently adds the whole 16 bit chunk of padding to original message (which really sounds like a bug). That is why the first part of encrypted Nodes' message corresponds to the one created in Python, the other one is just Node Crypto trying to encrypt its own excessive padding.
Anyway, to get rid of the error, i've simply added
aes.setAutoPadding(false);
in my encrypt() function.
Wooh.
A Perl script use this module to encrypt string
http://search.cpan.org/~zefram/Crypt-Eksblowfish-0.009/lib/Crypt/Eksblowfish.pm
I need to code the decrypt fonction in python . I know the key and the salt .
I tried to use py-bcrypt but it seems that the two equiv function
$ciphertext = $cipher->encrypt($plaintext);
$plaintext = $cipher->decrypt($ciphertext);
are not implemented .
How can i do ? Is there a python module anywhere that can help me to decrypt my strings ?
Update: The complete answer is the Perl code:
my $cipher = Crypt::EksBlowFish->new($cost, $salt, $key);
is equivalent to this Python code:
bf = Eksblowfish()
bf.expandkey(salt, key)
for i in xrange(cost << 1):
bf.expandkey(0, key)
bf.expandkey(0, salt)
See this repo for example code: https://github.com/erantapaa/python-bcrypt-tests
Original answer:
A partial answer...
I'm assuming you are calling this Perl code like this:
use Crypt::EksBlowfish;
my $cipher = Crypt::EksBlowFish->new($cost, $salt, $key);
$encoded = $cipher->encrypt("some plaintext");
The new method is implemented by the C function setup_eksblowfish_ks() in lib/Crypt/EksBlowfish.xs. This looks like it is the same as the expandKey method in the Python code (link)
The main difference is the $cost parameter which is not present in the Python method. In the Perl code the $cost parameter controls how many times this loop is executed after the key schedule has been set up:
for(count = 1U << cost; count--; ) {
for(j = 0; j != 2; j++) {
merge_key(j == 0 ? expanded_key : expanded_salt, ks);
munge_subkeys(ks);
}
}
The Perl ->encrypt() method enciphers a 64-bit word. The equivalent Python code is:
bf.cipher(xl, xr, bf.ENCRYPT)
where xl and xr are integers representing the left 32-bits and right 32-bits respectively.
So the recipe should go something like this:
Create the Python object: bf = EksBlowfish()
Initialize the key schedule: bf.expandkey(salt, key)
Further munge the key schedule using the cost parameter (TBD)
Encrypt with bf.cipher(xl, xr, bf.ENCRYPT)
I get different results when trying to compute SHA-1 digest in Python and C++.
Python code:
import hashlib
salt = 0xF0C020D239062F875C7BD8FB218D8102C9B37656F653E8DF0C655EF2D4A0CB61
password = 'pass1'
m = hashlib.sha1()
m.update( bytearray.fromhex(hex(salt)[2:-1]) )
m.update( password )
print m.hexdigest()
# output: e92f9504b2d46db0af7732c6e89e0260e63ae9b8
I extracted from the C++ code a snippet:
BigNumber salt, x;
Sha1Hash xhash;
uint8 password[] = "pass1";
// salt is received from a network packet (32 bytes)
// 2014-08-16 16:06:37 --> salt=F0C020D239062F875C7BD8FB218D8102C9B37656F653E8DF0C655EF2D4A0CB61
salt.SetBinary(lc.salt, 32);
xhash.UpdateData(salt.AsByteArray(), salt.GetNumBytes());
xhash.UpdateData(password, sizeof(password) - 1);
xhash.Finalize();
x.SetBinary(xhash.GetDigest(), xhash.GetLength());
logdebug("--> x=%s", x.AsHexStr());
// output: E5B463090B335BBC734BD3F4683F310E87ED6E4A
How must I modify my Python code to have the same results as in C++?
You use different endiness in C++ and python. So you have to reverse the bytes of your salt in python.
import hashlib
salt = 'F0C020D239062F875C7BD8FB218D8102C9B37656F653E8DF0C655EF2D4A0CB61'.decode('hex')
password = 'pass1'
m = hashlib.sha1()
m.update( salt[::-1] )
m.update( password )
print m.hexdigest()
# output: 4a6eed870e313f68f4d34b73bc5b330b0963b4e5 <- the reversed of the C++ result
SHAs are specifications, hence some initial state can be implementation dependent.
I suggest to use the very same implementation if you need to compare Python and C++ codes.