AES GCM - ciphertext is different everytime - encryption

I have the following code in python:
from Cryptodome.Cipher import AES
key = b'\x19\x3c\x04\x27\xf6\xa2\x1a\x08\x7f\xb0\x1c\xe9\xa7\x92\x3d\xd8\xe5\xc7\xae\x98\xf5\x0b\xf4\xa3\x7b\x4f\xd6\x76\x95\xd3\xe0\xd9'
data = b'\x47\x56\x64\xc1\x54\xe7\xa1\xed\x92\x4b\x26\x06\x6f\x4e\x95\x30\xea\xc4\x08\xf7\xdc\x69\x1c\x38\x38\xff\x38\x58\x50\x78\x9f\xd9\x32\xea\x86\xcd\x8c\x6d\x8f\x17\x30\x2a\xae\x6e\xa1\xc6\x6d'
header = b'\xdb\xd2\xed\x76\x3c\x50\xf2\x7c\xe3\xbe\x78\xa9\xc3\x6b'
cipher = AES.new(key, AES.MODE_GCM)
cipher.update(header)
cipherText, tag = cipher.encrypt_and_digest(data)
print(cipherText)
The ciphertext seemed to be changing everytime.
Any idea why that is?

Related

Encryption with Crypto.js and pycrypto generates different encryption cipher text

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.

Padding is incorrect. AES encryption

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.

RSA Encryption in Jmeter for Response Value

I am currently working in a application where RSA Encryption is being used for Encrypting sensitive data. Is there a way to use RSA Encryption in Jmeter so that this value appears encrypted in the previous response? Below is an example,
The actual value,
"emailAddressInfo" : ("PerfTestA#gmail.com | PerftestB#gmail.com").
I want the above value to appear encrypted like below in Jmeter.
"emailAddressInfo": "(as15114677sfh0hfu4scr460hdhsb24sjj324j689hsakw42cjjftks4wefe4sd)"
The mix of characters represent the encrypted value.
It doesn't look like an RSA-encrypted message to me so I would recommend double-checking the algorithm.
Whatever, in order to RSA-encrypt a string you need to use Cipher class from JSR223 Test Elements using Groovy language
Example code:
import javax.crypto.Cipher
import java.security.KeyFactory
import java.security.spec.X509EncodedKeySpec
//replace with your own RSA public key
def publicKey = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsvX9P58JFxEs5C+L+H7W\n' +
'duFSWL5EPzber7C2m94klrSV6q0bAcrYQnGwFOlveThsY200hRbadKaKjHD7qIKH\n' +
'DEe0IY2PSRht33Jye52AwhkRw+M3xuQH/7R8LydnsNFk2KHpr5X2SBv42e37LjkE\n' +
'slKSaMRgJW+v0KZ30piY8QsdFRKKaVg5/Ajt1YToM1YVsdHXJ3vmXFMtypLdxwUD\n' +
'dIaLEX6pFUkU75KSuEQ/E2luT61Q3ta9kOWm9+0zvi7OMcbdekJT7mzcVnh93R1c\n' +
'13ZhQCLbh9A7si8jKFtaMWevjayrvqQABEcTN9N4Hoxcyg6l4neZtRDk75OMYcqm\n' +
'DQIDAQAB'
def x509PublicKey = new X509EncodedKeySpec(publicKey.decodeBase64())
def keyFactory = KeyFactory.getInstance('RSA')
def key = keyFactory.generatePublic(x509PublicKey)
def string2Encrypt = '("PerfTestA#gmail.com | PerftestB#gmail.com").'
def encryptCipher = Cipher.getInstance('RSA')
encryptCipher.init(Cipher.ENCRYPT_MODE, key)
def secretMessage = string2Encrypt.getBytes('UTF-8')
def encryptedMessage = encryptCipher.doFinal(secretMessage)
def encodedMessage = encryptedMessage.encodeBase64().toString()
vars.put('encodedMessage', encodedMessage)
Once done you will be able to refer the encrypted value as ${encodedMessage} where required.

Python AES Decryption initialization vector & key value- size issue

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.

Using CTR mode in DES algorithm (in python)

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)

Resources