I am trying to generate encrypted text in Node using Crypto.js using its AES algorithm. It works in Js for encryption and decryption both. Similarly I tried implementing same in Python using pycrypto and it does encryption and decryption both in python. But the problem arises when I want to use encrypted cipher text from JS to decrypt in Python. The problem is that the encrypted text generated in JS is different from what is generated in Python.
Here is the JS based usage of AES:
import CryptoJS from "crypto-js";
let str = "lol";
let salt = "ABCDEFGHIJKLMNOP";
let key = "ABCDEFGHIJKLMNOPABCDEFGHIJKLMNOP";
salt = CryptoJS.enc.Hex.parse(salt);
key = CryptoJS.enc.Hex.parse(key);
const options = {
iv: salt,
padding: CryptoJS.pad.ZeroPadding,
mode: CryptoJS.mode.CFB,
};
// function to encrypt the string
function encrypt(str, salt, key) {
const cipher = CryptoJS.AES.encrypt(str, key, options);
console.log("cipher", CryptoJS.enc.Hex.stringify(cipher.ciphertext));
return cipher.ciphertext.toString(CryptoJS.enc.Base64);
}
// function to decrypt the string
function decrypt(str, salt, key) {
const cipher = CryptoJS.AES.decrypt(str, key, options);
return cipher.toString(CryptoJS.enc.Utf8);
}
const encrypted = encrypt(str, salt, key);
console.log("encrypted", encrypted);
const decrypted = decrypt(encrypted, salt, key);
console.log("decrypted", decrypted);
// OUTPUT
// encrypted sd4Xpz/ws8x2j+cgF17t6A==
// decrypted lol
Here is the Python based usage of AES:
from Crypto.Cipher import AES
from base64 import b64encode, b64decode
str = "lol";
salt = b"ABCDEFGHIJKLMNOP";
key = "ABCDEFGHIJKLMNOPABCDEFGHIJKLMNOP";
enc_dec_method = 'utf-8'
def encrypt(str_to_enc, str_key, salt):
aes_obj = AES.new(str_key.encode('utf-8'), AES.MODE_CFB, salt)
hx_enc = aes_obj.encrypt(str_to_enc.encode('utf8'))
df = b64encode(hx_enc)
mret = b64encode(hx_enc).decode(enc_dec_method)
return mret
def decrypt(str_to_dec, str_key, salt):
aes_obj = AES.new(str_key.encode('utf-8'), AES.MODE_CFB, salt)
str_tmp = b64decode(str_to_dec.encode(enc_dec_method))
str_dec = aes_obj.decrypt(str_tmp)
mret = str_dec.decode(enc_dec_method)
return mret
test_enc_text = encrypt(str, key, salt)
test_dec_text = decrypt(test_enc_text, key, salt)
print(f"Encrypted Text: {test_enc_text}")
print(f"Decrypted Text: {test_dec_text}")
# OUTPUT
# Encrypted Text: o9XB
# Decrypted Text: lol
I tired encrypting and decrypting in both the languages and check the the documentation and source code on both the libraries.
Did also trying checking if I am missing encoding or decoding, but had no luck.
What key concept am I missing here which can help bridge the gap of compatibility here?
The codes are incompatible because:
Key and IV are not hex encoded, so the hex encoder must not be used in the CryptoJS code. If Utf-8 encoding is to be applied as in the Python code, the Utf-8 encoder must be used instead.
salt = CryptoJS.enc.Utf8.parse(salt);
key = CryptoJS.enc.Utf8.parse(key);
CFB is a stream cipher mode that does not require padding. However, in the current CryptoJS code, Zero padding is used (CryptoJS.pad.ZeroPadding). Instead, the padding has to be disabled (which does not happen implicitly, unlike in the Python code):
padding: CryptoJS.pad.NoPadding,
CFB is configured with an additional parameter, the segment size, which specifies the number of bits encrypted per encryption step. The CryptoJS code supports only one segment size, 128 bits. With PyCryptodome the segment size can be configured, by default 8 bits are used. For compatibility it must be changed to 128 bits:
aes_obj = AES.new(str_key.encode('utf-8'), AES.MODE_CFB, salt, segment_size=128)
With these changes both codes are compatible.
Test: Both codes provide the following ciphertext for the following plaintext:
plaintext: The quick brown fox jumps over the lazy dog
ciphertext: m0T/7e04eV49RTcgd7KtwHxSOavNzHNwlrvjt1YmmHidBy4rHS0oovclKQ==
Note that what you call salt is actually the initialization vector (IV). The term salt is more commonly used in the context of key derivation functions (like PBKDF2).
Regarding security: The values used for salt and IV are OK for testing, but in a real world scenario a random byte sequence has to be applied for the IV for each encryption, which has to be passed along with the ciphertext to the decrypting side (generally concatenated).
Also, as key no passphrase has to be used, but a random byte sequence. A passphrase (a strong one of course) may only be applied in combination with a key derivation function.
Related
encrypted=b'$\x940\xc6\xef3s\x01|e\xe8\xdf\xaa00\xcc\x8c\x98i\x84\xcc[e\xd2\xa4]\xb37\xab\xbc#} \xaeC\xdd\xd7\x89\xdd[\xab)\xf0t\xa8\x95\xfe\xf9>\x15\x02:\x85-s*\x0cf\xe1\xa5\xc3\xa8]\x1d\x8f\xe2f\x1aje\x9f\xc3^\xe0\xc6i\xd6\x02$I\xce0_\x97\x8c\x06Rj\x1e\xe0F\x1fE\xfb\xf4\x0be:\x01\xd1m\xe4\xfaVv\xa0\xdf\xa8\x1e\xec\x03\xc1\x9a\x89\x9a\xa1m\xb4uL\x97\x81\xdc\xaaG5\xf7Q\xba\xc6\xf33\x05\x94l#pL!\x886\x9a5\xa3\xa2Z)\xd3\x80&p\xa4)\x1e\x9ei\xcc\xa9\xcc\xe9j\xe9\xb4\x14\xe1<\x8c\xa50\xd4\xf9\xb5\xb2\xd4#3\nwW#m\x14\xb67\xf3!a\x19:\x8af\x1c\xf1\x13\xbfM\x1a\xfe\xaf\xb0\x95Y~\xe2}\x06\xf0\xc1\xff\xde\x04\x06\xc9\xb4\xfe\xc8\xbb\x8eS\xff\xe29Q\xa0\xf6\xd2\xc78\xf5\x97\x07OW\xbb\xfd\x08q\x8f4C\xbb\x00\xa0\xa1_g\x0e3&x\\\x1a\xa4/a\xa2\x1bp|\x84\xdfr[.\xe5\xbd\xb1\xfc\xc3\t_0l\xddU\xc8HL#&LD\x17g=\xb5\xb0\xcf'
mk123=b'$\x940\xc6\xef3s\x01|e\xe8\xdf\xaa00\xcc\x8c\x98i\x84\xcc[e\xd2\xa4]\xb37\xab\xbc#} \xaeC\xdd\xd7\x89\xdd[\xab)\xf0t\xa8\x95\xfe\xf9>\x15\x02:\x85-s*\x0cf\xe1\xa5\xc3\xa8]\x1d\x8f\xe2f\x1aje\x9f\xc3^\xe0\xc6i\xd6\x02$I\xce0_\x97\x8c\x06Rj\x1e\xe0F\x1fE\xfb\xf4\x0be:\x01\xd1m\xe4\xfaVv\xa0\xdf\xa8\x1e\xec\x03\xc1\x9a\x89\x9a\xa1m\xb4uL\x97\x81\xdc\xaaG5\xf7Q\xba\xc6\xf33\x05\x94l#pL!\x886\x9a5\xa3\xa2Z)\xd3\x80&p\xa4)\x1e\x9ek\xcc\xa9\xcc\xe9j\xe9\xb4\x14\xe1<\x8c\xa50\xd4\xf9\xb5\xb2\xd4#3\nwW#m\x14\xb67\xf3!a\x19:\x8af\x1cq\x13\xbfM\x1a\xfe\x8f\xb0\x95Y~\xe2}\x06\xf0\xc1\xff\xde\x14\x06\xc9\xb4\xfe\xc8\xb3\x8eS\xff\xe29Q\xa0\xf6\xd2\xc78\xf5\x97\x07OW\xbb\xfd\x08q\x8f4C\xbb\x00\xa0\xa1_g\x0es&x\\\x1a\xa4/a\xa2\x1bp|\x84\xdbr[.\xe5\xbd\xb1\xbc\xe3\t_0L\xd9U\xc8HL#&LD\x17g=\xb5\xb0\xcf'
note:ive done some process in between so the bytestrings mk123 and encrypted are not the same
the first byte string is the one ive got after encryption,but when i try to decode its throwing the error"padding is incoreect"
this is the code ive used
from hashlib import md5
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad, unpad
class AESCipher:
def __init__(self, key):
password = key.encode('utf-8')
self.key = md5(password).digest()
def encrypt(self, data):
vector = get_random_bytes(AES.block_size)
encryption_cipher = AES.new(self.key, AES.MODE_CBC, vector)
return vector + encryption_cipher.encrypt(pad(data, AES.block_size))
def decrypt(self, data):
file_vector = data[:AES.block_size]
decryption_cipher = AES.new(self.key, AES.MODE_CBC, file_vector)
return unpad(decryption_cipher.decrypt(data[AES.block_size:]), AES.block_size)
the driver code is
message='0000000000000000011111111111111001000010001011100101101011101110010000100010111001011010111011100101101000100010011111111111111001111111111111100101111011100010010111101110101001011110111010100101111011101010010001100010001001111111111111100000000000000000'
msg = message.encode('utf-8')
pwd = "password"
encrypted = AESCipher(pwd).encrypt(msg)
print('Ciphertext:', encrypted)
decrypted = AESCipher(pwd).decrypt(mk123).decode('utf-8')
If you change the last block or the previous to last block of ciphertext then unpadding may indeed fail for CBC mode. This is because both of these blocks change the resulting plaintext. Generally, for CBC mode, you'll change the bits of one block with a chance of 0.5 for the block that you changed, and the same bits of the plaintext at the next block are flipped. How changes affect the decryption routine is called "error propagation".
You can figure most of this out yourself by looking at the decryption diagram from Wikipedia (by WhiteTimberwolf):
Knowing that, if you want to investigate the original plaintext you simply need to skip unpadding. Then you get the above changes made to the plaintext (including padding). You can then try to fix or guess the plaintext and/or correct the padding. If you cannot correct the padding then the size of the plaintext can differ by one block - as far as the cipher is concerned anyway.
I am writing Python Decryption logic to decrypt the message (client-side it is encrypted using Java).
Python code:
def decrypt(self, text):
decode = base64.b64decode(text)
cryptor = AES.new(InKey, MODE_CBC, InIV)
plain_text = cryptor.decrypt(decode)
return unpad(plain_text)
I have received below key & IV values (sample values) from the client team:
InKey =
"6asd6587daIs8g2qvi3rJbM9sdasd6cb2kdYC0TOy5zEgTo+8LrQn0UJZAmJCtmX......"(it
is length of 684 chars) InIV =
"7as76cascsagoKtID7z1nUakJqzj+Dwl9cL9Q2/zBFbs0Sg3Kw6US8yvvzbkyg2bnjGHWofIWrhMQ/Bcde...."
(it is length of 684 chars)
Question: While running the above python code, getting the below error. How to convert the above IV value into 16bytes in size and also key-value length?
ValueError: Incorrect IV length (it must be 16 bytes long)
existing Decryption Java Client follows Hybrid pattern(first using RSA private key to pull two params(IV & Key)...and then AES cipher to decrypt actual message):
Pseudo code:
................................
//Step-1) Generate RSA-PrivateKey
................................
RSAPrivateKey serverPrivateKey = KeyUtils.getPrivateKey("lib", PrivKey);
/* here PrivKey refers to .der certificate
and FYI- KeyUtils actually refers to a class, that generate RSA PrivateKey with below code:
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
KeySpec ks = new PKCS8EncodedKeySpec(privKeyBytes);
return (RSAPrivateKey)keyFactory.generatePrivate(ks);
*/
................................
//Step-2) use RSA-PrivateKey to decrypt the message
................................
MessageEncryption encryptor = new MessageEncryption(serverPrivateKey);
/* initializes Cipher(RSA instance) with passed RSA PrivateKey
this.m_asymmetricalKey = serverPrivateKey;
this.m_asymmetricalCipher = Cipher.getInstance(this.m_asymmetricalKey.getAlgorithm());
*/
encryptor.decryptMessage(JSONresponseBody); // invokes below mentioned method
...............
decryptMessage(JSONObject jsonMessage)
{
.............................
IvParameterSpec initializationVector = new IvParameterSpec(this.m_asymmetricalCipher.doFinal(StringCodec.decodeBase64(jsonMessage.getString("iv").getBytes())));
this.m_asymmetricalCipher.init(2, this.m_asymmetricalKey);
Key secretKey = new SecretKeySpec(this.m_asymmetricalCipher.doFinal(StringCodec.decodeBase64(jsonMessage.getString("key").getBytes())), "AES");
Cipher symmetricalCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
symmetricalCipher.init(2, secretKey, initializationVector);
return new String(symmetricalCipher.doFinal(StringCodec.decodeBase64(jsonMessage.getString("message").getBytes())));
.............................
}
..............
So at Python how to get right key-size & IV, is there way to implement similar hybrid pattern at java or does it any other approach to deal with at python ?
Thanks.
Your Java-side code does the following things:
Read the PKCS8-encoded RSA key
Decode the base64-encoded IV
Decrypt the IV using the private key (a side-note: encrypting IV makes very little sense to me)
Decode the base64-encoded AES key
Decrypt the AES key using the private key
Decode the base64-encoded cyphertext
Decrypt the ciphertext using the obtained IV and AES key
You need to re-implement each of the above steps in Python. Some are trivial, like base64-decoding. The others depend on the cryptography libraries you're using.
I have the following requirements regarding encryption of url parameters. As I am newly to CryptoJS I can not figure out how to perform all steps using CryptoJS. Can anybody help me with example code to perform this steps?
Encrypt Requirements
Generate a secret key from the given password.
Receive the password which is coupled with the system name. (This is already available)
Generate a random nonce in byte-array as the password’s salt which has a length 16-bytes.
Set the number of iterations for secret key as 65536.
Set the length of secret key as 256-bit (not bytes).
Set the algorithm name for key derivation using PBKDF2 with HMAC and SHA-2.
Set the algorithm name of key generation as “AES”.
Create a secret key with the function of library or tool with all sets above.
Encrypt the JSON-string with the above generated secret key.
Generate a random nonce in byte-array as the IV (initial vector) which has a length 12-bytes for encryption.
Select encrypt mode for the crypto in your function or tool for encryption.
Set the name of crypto algorithm which is for AES in GCM mode and without any padding.
Set the length of authentication tag for GCM as 128-bits (not bytes).
Convert your JSON-string which contains customer information to a byte-array(plainText) with character set UFT-8.
Encrypt the plainText with all sets and the secret key in last step to a new byte-array(cipherText)
Prefix the generated IV and password’s salt to the cipherText .
Create a new byte-array (cipherTextWithIvSalt).
Add the 12-bytes IV in cipherTextWithIvSalt at first.
Add the 16-bytes password’s salt in cipherTextWithIvSalt after that.
Add the cipherText in cipherTextWithIvSalt at last.
cipherTextWithIvSalt is now in form “IV + salt + cipherText”
Encode the cipherTextWithIvSalt.
Encode the cipherTextWithIvSalt to a new byte-array (encodedCipherTextWithIvSalt) by using Base64
Build a displayable encrypted string.
Build a new string from byte-array encodedCipherTextWithIvSalt with character set UTF-8.
I have tried to do it myself but it does not work here is the example of my js code:
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
let secretKey = generateKey(salt);
let iv = CryptoJS.lib.WordArray.random(96 / 8);
let JSON_String = "{"Name":"Max", "Firstname":"Mustermann"}";
let plainText = CryptoJS.enc.Utf8.parse(JSON_String);
let cipherText = encryptJSONString(plainText, secretKey, iv);
let cipherTextWithIvSalt = prefixCipherTextWithSaltAndIV(iv, secretKey, cipherText);
let encodedCipherTextWithIvSalt = CryptoJS.enc.Base64.parse(cipherTextWithIvSalt);
let displayableText = CryptoJS.enc.Utf8.stringify(encodedCipherTextWithIvSalt);
function generateKey(password) {
var salt = CryptoJS.lib.WordArray.random(128 / 8);
return CryptoJS.PBKDF2(password, salt, { keySize: 256 / 32, iterations: 65536, hasher: CryptoJS.algo.SHA256 });
}
function encryptJSONString(plainText, secretKey, iv) {
//mode: CryptoJS.mode.GCM
return CryptoJS.AES.encrypt(plainText, secretKey, { iv: iv, padding: CryptoJS.pad.NoPadding });
}
function prefixCipherTextWithSaltAndIV(iv, secretKey, cipherText) {
let byteArray = [];
byteArray.push(iv.words);
byteArray.push(secretKey.words);
byteArray.push(cipherText);
return byteArray;
}
Thank you very much in advance!
I found encrypt package for flutter to encrypt and decrypt a message, I succeded to encrypt and decrypt but don't succeded to use this in a cas of separated device like illustrated.
Here is an example, where I compose a plaintext with a static string and a random number, it permit to change the generated key and when I decrypt, found the static string with regex match
var number = new Random();
var random= number.nextInt(100);
final plainText = 'static_name$random';
final key = Key.fromLength(16);
final iv = IV.fromLength(8);
final encrypter = Encrypter(AES(key));
final encrypted = encrypter.encrypt(plainText, iv: iv);
final decrypted = encrypter.decrypt(encrypted, iv: iv);
print(encrypted.base64);//my plaintext is encrypted fesesgesgneslg465esg6es4g
print(decrypted); //my random plaintext is decrypted static_name$rnd
//my regex match function
Currently I don't find how to enter my encrypted key ( fesesgesgneslg465esg6es4g) I serach to do something like that
//---------on press I generate a random key that I encrypt----
var rng = new Random();
var rnd= rng.nextInt(100); //choisir le nombre max de contenu de la catégorie
final plainText = 'static_name$rnd';
final key = Key.fromLength(16);
final iv = IV.fromLength(8);
final encrypter = Encrypter(AES(key));
final encrypted = encrypter.encrypt(plainText, iv: iv);
//output : 68e4sg68es4ges68g4
//---------the user enter the key(68e4sg68es4ges68g4) on a second device ----
encrypted=68e4sg68es4ges68g4;
final key = Key.fromLength(16);
final iv = IV.fromLength(8);
final encrypter = Encrypter(AES(key));
final decrypted = encrypter.decrypt(encrypted, iv: iv);
print(decrypted);
I can't found how to decrypt my key ( 68e4sg68es4ges68g4)
In few word I succeded to encrypt and decrypt automatically an input but don't succeded to manually add the generated key to the decrypt function.
There's a serious problem with how you are trying to use the encrypt package. By using Key.forLength() you are basically using a key of 0000000....000. The same for IV. That's not a very secure key!
When using a crypto system between two different machines you need to find some way to share the key between them - the so-called "shared secret". You might generate this from a "passphrase" using, for example, PKKDF2. Or you could just compile a random byte string into the code, but expect that an attacker could reverse-engineer your code.
Using AES, you should not use the same IV twice with the same key, which is why cryptosystems typically generate a key and an initial IV uniquely during the key exchange, and then use something (like a message sequence number) to change the IV for each message that's encrypted with that one key.
For test purposes, like yours, you might want to generate a fixed 16 bit key and fixed 16 bit IV using a random number generator like this. Then use the .fromBase64() constructors.
var key = Key.fromBase64('yE9tgqNxWcYDTSPNM+EGQw=='); // obviously, insert your own value!
var iv = IV.fromBase64('8PzGKSMLuqSm0MVbviaWHA==');
Use the same values for key and IV in the encrypting and decrypting app.
You can use this method:
String encrypted = "68e4sg68es4ges68g4";
final decrypted = encrypter.decrypt(encrypt.Encrypted.fromBase64(encrypted),iv:iv);
I want to use CTR mode in DES algorithm in python by using PyCryptodome package. My code presented at the end of this post. However I got this error: "TypeError: Impossible to create a safe nonce for short block sizes". It is worth to mention that, this code work well for AES algorithm but it does not work for DES, DES3 , Blowfish and etc (with 64 block size). To my knowledge CTR mode can be applied in the 64 block cipher algorithms.
from Crypto.Cipher import DES
from Crypto.Random import get_random_bytes
data = b'My plain text'
key = get_random_bytes(8)
cipher = DES.new(key, DES.MODE_CTR)
ct_bytes = cipher.encrypt(data)
nonce = cipher.nonce
cipher = DES.new(key, DES.MODE_CTR, nonce=nonce)
pt = cipher.decrypt(ct_bytes)
print("The message was: ", pt)
Thanks alot.
The library defines the nonce
as that part of the counter block that is not incremented.
Since the block is only 64 bits long, it is hard to securely define how long that nonce should be,
given the danger of wraparound (if you encrypt a lot of blocks) or nonce reuse (if you generate the nonce randomly).
You can instead decide that the nonce is not present, the counter takes the full 64 bits and a random initial value.
iv = get_random_bytes(8)
cipher = DES.new(key, nonce=b'', initial_value=iv)
Finally, I guess that this is only an exercise.
DES is a very weak cipher, with a key length of only 56 bits and a block size of only 64 bits.
Use AES instead.
bs = DES.block_size
plen = bs - len(plaintext) % bs
padding = [plen] * plen
padding = pack('b' * plen, *padding)
key = get_random_bytes(8)
nonce = Random.get_random_bytes(4)
ctr = Counter.new(32, prefix=nonce)
cipher = DES.new(key, DES.MODE_CTR,counter=ctr)
ciphertext = cipher.encrypt(plaintext+padding)