I need to sent my friend Bob the number 42, my code will use a pre-shared key generate a random number to encrypt the message (42) and decrypt it on Bobs end. the only problem is, I have no clue how to generate a repeatable encryption key in python.
You can use Crypto to encrypt a message using AES.
import hashlib, base64
from Crypto import Random
from Crypto.Cipher import AES
class AESCipher():
def __init__(self, key):
self.private_key = hashlib.sha256(key.encode()).digest()
self.bs = AES.block_size
def encrypt(self, data):
# generate public key
public_key = Random.new().read(self.bs)
# setup AES Cipher using public key and private key
cipher = AES.new(self.private_key, AES.MODE_CBC, public_key)
# enrpyt the data and convert to base64
return base64.b64encode(public_key + cipher.encrypt(self.pad(data).encode()))
def decrypt(self, enc):
# convert encrypted data to base 64
enc = base64.b64decode(enc)
# get public key
public_key = enc[:AES.block_size]
# setup AES Cipher using public and private key
cipher = AES.new(self.private_key, AES.MODE_CBC, public_key)
# decrypt data using the public key
return self.unpad(cipher.decrypt(enc[AES.block_size:])).decode("utf-8")
def pad(self, s):
# pads data so that it's a multiple of 16
return s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs)
def unpad(self, s):
# removes padding
return s[:-ord(s[len(s)-1:])]
cipher = AESCipher("your secret key")
Here I encrypt some text, the public key with the encrypted text is joined and returned from the encrypt method, you can then send the returned text to Bob, to decrypt the text bob then just needs to run the decrypt method.
>>> cipher.encrypt("your message")
b'HYfUkcd//CaRquG9AhReR8bJYdVQdcGWRAjcp9AstLs='
>>> output = cipher.encrypt("your message")
>>> output
b'RVTK7L7ZDw9DzvuXuj8zYPZJjBO/A0N3l5N1hp9LY6U='
>>> cipher.decrypt(output)
'your message'
>>>
Related
I have this Python method on the server to encrypt a string into bytes (AES/CBC).
class AESCipher(object, key):
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()))
def decrypt(self, enc):
enc = base64.b64decode(enc)
iv = enc[:AES.block_size]
cipher = AES.new(self.key, AES.MODE_CBC, iv)
return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode('utf-8')
def _pad(self, s):
return s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs)
The output of encrypt() is in bytes like this: b'PMgMOkBkciIKfWy/DfntVMyAcKtVsM8LwEwnTYE5IXY='
I would like to store this into database, and send it as string via API to Kotlin. And in there I would like to decrypt it via the same shared secret key.
In what format do I save the bytes above into database?
Once arrived in Kotlin client, how do I convert that string into ByteArray?
My theory is that I have to store the bytes as base64 string in the database.
And on the other side I have to decode the string as base64 into bytes. Is this approach correct? Will the encryption/decryption work like this end-to-end with the code below?
fun decrypt(context:Context, dataToDecrypt: ByteArray): ByteArray {
val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING")
val ivSpec = IvParameterSpec(getSavedInitializationVector(context))
cipher.init(Cipher.DECRYPT_MODE, getSavedSecretKey(context), ivSpec)
val cipherText = cipher.doFinal(dataToDecrypt)
val sb = StringBuilder()
for (b in cipherText) {
sb.append(b.toChar())
}
return cipherText
}
fun getSavedSecretKey(context: Context): SecretKey {
val sharedPref = PreferenceManager.getDefaultSharedPreferences(context)
val strSecretKey = sharedPref.getString("secret_key", "")
val bytes = android.util.Base64.decode(strSecretKey, android.util.Base64.DEFAULT)
val ois = ObjectInputStream(ByteArrayInputStream(bytes))
val secretKey = ois.readObject() as SecretKey
return secretKey
}
fun getSavedInitializationVector(context: Context) : ByteArray {
val sharedPref = PreferenceManager.getDefaultSharedPreferences(context)
val strInitializationVector = sharedPref.getString("initialization_vector", "")
val bytes = android.util.Base64.decode(strInitializationVector, android.util.Base64.DEFAULT)
val ois = ObjectInputStream(ByteArrayInputStream(bytes))
val initializationVector = ois.readObject() as ByteArray
return initializationVector
}
UPDATE
I have tried to remove the Base64 to remove the memory overhead as suggested.
Python:
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 iv + cipher.encrypt(raw.encode())
So this is no longer possible.
enc = AESCipher('abc').encrypt("myLife")
value_to_save_in_db = enc.decode("utf8")
So I need to find a way to store the byte array directly in the database. I think I should be able to do this as blob. But some challenges remain as how to send the bytearray as part of JSON over the API to the android device. I think I have to convert it to Base64 string again. Not sure if I have gained anything in that case...
The following Kotlin code:
val decrypted = decrypt("blEOKMQtUbNOzJbvEkL2gNhjF+qQ/ZK84f2ADu8xyUFme6uBhNYqvEherF/RRO9YRImz5Y04/ll+T07kqv+ExQ==");
println(decrypted);
decrypts a ciphertext of the Python code. Here decrypt() is:
fun decrypt(dataToDecryptB64 : String) : String {
// Base64 decode Python data
val dataToDecrypt = Base64.getDecoder().decode(dataToDecryptB64)
// Separate IV and Ciphertext
val ivBytes = ByteArray(16)
val cipherBytes = ByteArray(dataToDecrypt.size - ivBytes.size)
System.arraycopy(dataToDecrypt, 0, ivBytes, 0, ivBytes.size)
System.arraycopy(dataToDecrypt, ivBytes.size, cipherBytes, 0, cipherBytes.size)
// Derive key
val keyBytes = MessageDigest.getInstance("SHA256").digest("abc".toByteArray(Charsets.UTF_8))
// Decrypt
val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING")
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(keyBytes, "AES"), IvParameterSpec(ivBytes))
val cipherText = cipher.doFinal(cipherBytes)
return String(cipherText, Charsets.ISO_8859_1)
}
For this, the ciphertext was generated using the posted Python class AESCipher as follows:
plaintext = 'The quick brown fox jumps over the lazy dog'
cipher = AESCipher('abc')
ciphertext = cipher.encrypt(plaintext)
print(ciphertext.decode('utf8')) # Base64 string, which can be stored e.g. in a DB
I applied the originally posted Python implementation that derives the key using SHA256. However, if the key is derived from a password, for security reasons not SHA256 but a reliable key derivation function, e.g. Argon2 or PBKDF2, should be used.
The Kotlin code first Base64 decodes the Python data and then separates IV and the actual ciphertext. Then, the key is derived by generating the SHA256 hash of the password. Finally the data is decrypted.
The current Python code Base64 encodes the data so that it can be stored as a string in the DB. Alternatively, the Python code could be modified so that no Base64 encoding is performed, and the raw data can be stored (which requires less memory, Base64 overhead: 33%).
Depending on the solution chosen, the Kotlin code may or may not need to Base64 decode the data.
I am currently trying to use AES cryptography to encrypt and decrypt a string that always has a length of 9 characters. What I am trying to do is to encrypt the string in swift and then decrypt that encrypted string in python. I am using AES encryption with CryptoSwift and decrypting with PyCryptodome.
This is what my function in swift looks like:
import CryptoSwift
func crypto_testing() {
print("Cryptography!")
let ivString = "0000000000000000"
let keyString = "This is a key123"
let key = [UInt8](keyString.utf8)
let iv = [UInt8](ivString.utf8)
let stringToEncrypt = "123456789"
let enc = try! aesEncrypt(stringToEncrypt: stringToEncrypt, key: key, iv: iv)
print("ENCRYPT:",enc)
}
func aesEncrypt(stringToEncrypt: String, key: Array<UInt8>, iv: Array<UInt8>) throws -> String {
let data = stringToEncrypt.data(using: String.Encoding.utf8)
let encrypted = try AES(key: key, blockMode: CFB(iv: iv), padding: .noPadding).encrypt((data?.bytes)!)
return encrypted.toHexString() //result
}
The result I get from running the crypto_testing function is:
Cryptography!
ENCRYPT: 5d02105a49e55d2ff7
Furthermore, this is what my decryption function looks like in python:
import binascii
from Crypto.Cipher import AES
KEY = b'This is a key123'
IV = b'0000000000000000'
MODE = AES.MODE_CFB
def decrypt(key, iv, encrypted_text):
aes = AES.new(key, MODE, iv)
encrypted_text_bytes = binascii.a2b_hex(encrypted_text)
decrypted_text = aes.decrypt(encrypted_text_bytes)
return decrypted_text
decrypted_text = decrypt(KEY, IV, encrypted_text)
print(decrypted_text)
And the result from plugging in the encrypted message into the decrypt function like so:
>>> decrypt(b'This is a key123', b'0000000000000000', '5d02105a49e55d2ff7')
b'1%\xdc\xc8\xa0\r\xbd\xb8\xf0'
If anyone has any clue as to what is going wrong here that would be a great help.
Try this:
let stringToEncrypt = "123456789"
var aes: AES
var encrypted: [UInt8]
do {
aes = try AES(key: key, blockMode: CBC(iv: iv), padding: . noPadding)
encrypted = try aes.encrypt(stringToEncrypt.bytes)
}
let base64Encypted = encrypted.toBase64()```
I want to verify that CryptoJS's AES is actually implementing AES. However, I am not able to verify it by encrypting a plain using CryptoJS and decrypting the cipher with a python script using PyCrypto.
Source for CryptoJS:
http://ramblings.mcpher.com/Home/excelquirks/gassnips/cryptogs
https://script.google.com/d/1IEkpeS8hsMSVLRdCMprij996zG6ek9UvGwcCJao_hlDMlgbWWvJpONrs/edit?usp=sharing
I gathered that in CryptoJS-AES,
it does 1 iteration of MD5 on the passphrase to generate the symmetric key.
In the cipher, the first 16bytes is the salt in the form of "Salted__[8byte random]".
The salt is used as the iv for the encryption/decryption.
done in CBC-Mode
The cipher is created with the below code in Google App Script.
var encryptedMessage = CryptoJS.AES.encrypt ('this is my message to be encrypted', '0123456789abcdef').toString();
//var decryptedMessage = CryptoJS.AES.decrypt (encryptedMessage, '0123456789abcdef').toString(CryptoJS.enc.Utf8);
I then tried to decrypt the cipher with the below python code. (adapted from some stackoverflow thread)
import base64
import hashlib
from Crypto import Random
from Crypto.Cipher import AES
class AESCipher(object):
def __init__(self, key):
self.bs = 16
self.key = hashlib.md5(key.encode()).digest()
self.key = key
def decrypt(self, enc):
enc = base64.b64decode(enc)
iv = enc[:16]
cipher = AES.new(self.key, AES.MODE_CBC, iv)
return self._unpad(cipher.decrypt(enc[16:])).decode("utf-8")
def _pad(self, s):
return s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs)
#staticmethod
def _unpad(s):
print s
return s[:-ord(s[len(s)-1:])]
key = "0123456789abcdef"
aes = AESCipher(key)
cipher = "U2FsdGVkX1/CuZIB2c+sxY7rA0jw19EOKrL87cU1bzwU9BlIDP6VZvsSzXAQ+6v8S2GQOzf4VpEw7AK0LSFfKw=="
bbb = aes.decrypt(cipher)
print bbb #prints out gibberish
How can i verify that the AES is correct? Or what have i done wrong here?
CryptoGS is a direct port of CryptoJS and you can find the CryptoJS AES implementation at the following link:
https://github.com/brix/crypto-js/blob/develop/src/aes.js
CryptoGS' code is minified but the following gist breaks up some of the encryption algos into separate sections:
https://gist.github.com/DimuDesigns/5666e9c6df531b8d11099dfd291ed0e4
You can try running the AES algo in the gist through a code beautfier and comparing the two.
I'm trying to decrypt encrypdet data. Data was encrypted with AES CBC mode using pycryptodome lib.
Have a such error - "ValueError: Incorrect AES key length (256 bytes)
import os
from Crypto import Random
from Crypto.Cipher import AES
class AESCipher:
def __init__(self, key):
pass
def pad(self, s):
return s + b"\0" * (AES.block_size - len(s) % AES.block_size)
def encrypt(self, message, key, key_size=256):
message = self.pad(message)
iv = Random.new().read(AES.block_size)
cipher = AES.new(key, AES.MODE_CBC, iv)
return iv + cipher.encrypt(message)
def decrypt(self, ciphertext, key):
iv = ciphertext[:AES.block_size]
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = cipher.decrypt(ciphertext[AES.block_size:])
return plaintext.rstrip(b"\0")
def send_data(data)
key = os.urandom(16)
cipher = AESCipher(key)
ciphertext = cipher.encrypt(data, key)
return key, ciphertext
def receive_data(key, data):
cipher = AESCipher(key)
decrypted = cipher.decrypt(data, key)
return decrypted
data = b'12 43 42 46 af'
key, ciphertext = send_data(data)
decrypted = receive_data(key, data)
I think that what you want to decrypt is the ciphered text, not the original data (non-encrypted):
decrypted = receive_data(key, ciphertext)
Having a bit of trouble getting a AES cipher text to decrypt.
In this particular scenario, I am encrypting data on the client side with Crypto-JS and decrypting it back on a python server with PyCrypto.
encrypt.js:
var password = 'BJhtfRjKnTDTtPXUBnErKDxfkiMCOLyP';
var data = 'mytext';
var masterKey = CryptoJS.SHA256(password).toString();
// Derive keys for AES and HMAC
var length = masterKey.toString().length / 2
var encryptionKey = masterKey.substr(0, length);
var hmacKey = masterKey.substr(length);
var iv = CryptoJS.lib.WordArray.random(64/8);
var encrypted = CryptoJS.AES.encrypt(
data,
encryptionKey,
{
iv: iv,
mode: CryptoJS.mode.CFB
}
);
var concat = iv + encrypted;
// Calculate HMAC using iv and cipher text
var hash = CryptoJS.HmacSHA256(concat, hmacKey);
// Put it all together
var registrationKey = iv + encrypted + hash;
// Encode in Base64
var basemessage = btoa(registrationKey);
decrypt.py:
class AESCipher:
def __init__(self, key):
key_hash = SHA256.new(key).hexdigest()
# Derive keys
encryption_key = key_hash[:len(key_hash)/2]
self.key = encryption_key
self.hmac_key = key_hash[len(key_hash)/2:]
def verify_hmac(self, input_cipher, hmac_key):
# Calculate hash using inputted key
new_hash = HMAC.new(hmac_key, digestmod=SHA256)
new_hash.update(input_cipher)
digest = new_hash.hexdigest()
# Calculate hash using derived key from local password
local_hash = HMAC.new(self.hmac_key, digestmod=SHA256)
local_hash.update(input_cipher)
local_digest = local_hash.hexdigest()
return True if digest == local_digest else False
def decrypt(self, enc):
enc = base64.b64decode(enc)
iv = enc[:16]
hmac = enc[60:]
cipher_text = enc[16:60]
# Verify HMAC using concatenation of iv + cipher like in js
verified_hmac = self.verify_hmac((iv+cipher_text), self.hmac_key)
if verified_hmac:
cipher = AES.new(self.key, AES.MODE_CFB, iv)
return cipher.decrypt(cipher_text)
password = 'BJhtfRjKnTDTtPXUBnErKDxfkiMCOLyP'
input = 'long base64 registrationKey...'
cipher = AESCipher(password)
decrypted = cipher.decrypt(input)
I'm successful in re-calculating the HMAC but when I try and then decrypt the cipher I get something that seems encrypted with �'s in the result.
I was getting errors about input length of cipher text but when I switched to CFB mode that fixed it so I don't think it's a padding issue.
There are many problems with your code.
Client (JavaScript):
AES has a block size of 128 bit and CFB mode expects a full block for the IV. Use
var iv = CryptoJS.lib.WordArray.random(128/8);
The iv and hash variables are WordArray objects, but encrypted is not. When you force them to be converted to strings by concatenating them (+), iv and hash are Hex-encoded, but encrypted is formatted in an OpenSSL compatible format and Base64-encoded. You need to access the ciphertext property to get the encrypted WordArray:
var concat = iv + encrypted.ciphertext;
and
var registrationKey = iv + encrypted.ciphertext + hash;
registrationKey is hex-encoded. There is no need to encode it again with Base64 and bloat it even more:
var basemessage = registrationKey;
If you want to convert the hex encoded registrationKey to base64 encoding, use:
var basemessage = CryptoJS.enc.Hex.parse(registrationKey).toString(CryptoJS.enc.Base64);
concat is a hex-encoded string of the IV and ciphertext, because you forced the stringification by "adding" (+) iv and encrypted. The HmacSHA256() function takes either a WordArray object or a string. When you pass a string in, as you do, it will assume that the data is UTF-8 encoded and try to decode it as UTF-8. You need to parse the data yourself into a WordArray:
var hash = CryptoJS.HmacSHA256(CryptoJS.enc.Hex.parse(concat), hmacKey);
The CryptoJS.AES.encrypt() and CryptoJS.HmacSHA256() expect the key either as a WordArray object or as a string. As before, if the key is supplied as a string, a UTF-8 encoding is assumed which is not the case here. You better parse the strings into WordArrays yourself:
var encryptionKey = CryptoJS.enc.Hex.parse(masterKey.substr(0, length));
var hmacKey = CryptoJS.enc.Hex.parse(masterKey.substr(length));
Server (Python):
You're not verifying anything in verify_hmac(). You hash the same data with the same key twice. What you need to do is hash the IV+ciphertext and compare the result with the hash (called tag or HMAC-tag) that you slice off the full ciphertext.
def verify_hmac(self, input_cipher, mac):
# Calculate hash using derived key from local password
local_hash = HMAC.new(self.hmac_key, digestmod=SHA256)
local_hash.update(input_cipher)
local_digest = local_hash.digest()
return mac == local_digest
And later in decrypt():
verified_hmac = self.verify_hmac((iv+cipher_text), hmac)
You need to correctly slice off the MAC. The 60 that is hardcoded is a bad idea. Since you're using SHA-256 the MAC is 32 bytes long, so you do this
hmac = enc[-32:]
cipher_text = enc[16:-32]
The CFB mode is actually a set of similar modes. The actual mode is determined by the segment size. CryptoJS only supports segments of 128 bit. So you need tell pycrypto to use the same mode as in CryptoJS:
cipher = AES.new(self.key, AES.MODE_CFB, iv, segment_size=128)
If you want to use CFB mode with a segment size of 8 bit (default of pycrypto), you can use a modified version of CFB in CryptoJS from my project: Extension for CryptoJS
Full client code:
var password = 'BJhtfRjKnTDTtPXUBnErKDxfkiMCOLyP';
var data = 'mytext';
var masterKey = CryptoJS.SHA256(password).toString();
var length = masterKey.length / 2
var encryptionKey = CryptoJS.enc.Hex.parse(masterKey.substr(0, length));
var hmacKey = CryptoJS.enc.Hex.parse(masterKey.substr(length));
var iv = CryptoJS.lib.WordArray.random(128/8);
var encrypted = CryptoJS.AES.encrypt(
data,
encryptionKey,
{
iv: iv,
mode: CryptoJS.mode.CFB
}
);
var concat = iv + encrypted.ciphertext;
var hash = CryptoJS.HmacSHA256(CryptoJS.enc.Hex.parse(concat), hmacKey);
var registrationKey = iv + encrypted.ciphertext + hash;
console.log(CryptoJS.enc.Hex.parse(registrationKey).toString(CryptoJS.enc.Base64));
Full server code:
from Crypto.Cipher import AES
from Crypto.Hash import HMAC, SHA256
import base64
import binascii
class AESCipher:
def __init__(self, key):
key_hash = SHA256.new(key).hexdigest()
self.hmac_key = binascii.unhexlify(key_hash[len(key_hash)/2:])
self.key = binascii.unhexlify(key_hash[:len(key_hash)/2])
def verify_hmac(self, input_cipher, mac):
local_hash = HMAC.new(self.hmac_key, digestmod=SHA256)
local_hash.update(input_cipher)
local_digest = local_hash.digest()
return SHA256.new(mac).digest() == SHA256.new(local_digest).digest() # more or less constant-time comparison
def decrypt(self, enc):
enc = base64.b64decode(enc)
iv = enc[:16]
hmac = enc[-32:]
cipher_text = enc[16:-32]
verified_hmac = self.verify_hmac((iv+cipher_text), hmac)
if verified_hmac:
cipher = AES.new(self.key, AES.MODE_CFB, iv, segment_size=128)
return cipher.decrypt(cipher_text)
else:
return 'Bad Verify'
password = 'BJhtfRjKnTDTtPXUBnErKDxfkiMCOLyP'
input = "btu0CCFbvdYV4B/j7hezAra6Q6u6KB8n5QcyA32JFLU8QRd+jLGW0GxMQsTqxaNaNkcU2I9r1ls4QUPUpaLPQg=="
obj = AESCipher(password)
decryption = obj.decrypt(input)
print 'Decrypted message:', decryption