brix / crypto-js

JavaScript library of crypto standards.
Other
15.85k stars 2.39k forks source link

How to AES encrypt with php and AES decrypt with javascript? #135

Open sn01615 opened 6 years ago

sn01615 commented 6 years ago

php code

$pass = '123456';
$method = 'aes128';
$iv = '4f01bede9221586c';
$enc_data = openssl_encrypt('Message', $method, $pass, null, $iv);
echo $enc_data;
# response 8duGzD85Y2S3bU1h2Hu5ew==

javascript code

var bytes  = CryptoJS.AES.decrypt('8duGzD85Y2S3bU1h2Hu5ew==', '123456');
var plaintext = bytes.toString(CryptoJS.enc.Utf8);

console.log(plaintext);
// response empty
andriyslyusar commented 6 years ago

I do have the same problem? Any ideas?

cn-src commented 6 years ago

I also have similar doubts.

see https://code.google.com/archive/p/crypto-js/ I know default mode is 'CBC', and default padding is 'Pkcs7'. If i use custom Key and IV, like this:

var key = CryptoJS.enc.Hex.parse('000102030405060708090a0b0c0d0e0f'); var iv = CryptoJS.enc.Hex.parse('101112131415161718191a1b1c1d1e1f'); var encrypted = CryptoJS.AES.encrypt("Message", key, { iv: iv });

I can achieve the same way in java.

But, if like this: "CryptoJS.AES.encrypt('my message', 'secret key 123');" I do not know how to convert the password to key bytes, and default IV bytes.

DanBeard commented 6 years ago

I was curious too so I did some digging. The result from a call like: CryptoJS.AES.encrypt('my message', 'secret key 123');

returns an object that has all of the needed information to decrypt the ciphertext (even the key itself! so don't save that object anywhere! )

For example here I can build a lookup object with the key, iv and ciphertext in base64 encoded format:

var ciphertext = CryptoJS.AES.encrypt('my message', 'secret key 123');
console.log(JSON.stringify({key:ciphertext.key.toString(CryptoJS.enc.Base64), iv:ciphertext.iv.toString(CryptoJS.enc.Base64), ciphertext:ciphertext.ciphertext.toString(CryptoJS.enc.Base64)}))

I can then take that object, and get the original plaintext in, for example, python using it's cryptography library:

import os
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
from base64 import b64encode, b64decode

js = {"key":"FseXqPPUgnuscV5t4xajK+TyaIzhCr6ezjKHgeTO7HI=","iv":"JrMAW5tzT9umMrlWSdtSwg==","ciphertext":"3UIxU1zMdAjtAQ4XHltVWA=="}
# base64 decode the buffers for everything
iv = b64decode(js["iv"])
key = b64decode(js["key"])
ciphertext = b64decode(js["ciphertext"])

# construct a decryptor with the IV and key
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
d = cipher.decryptor()

#get the (padded) plaintext out
padded_plaintext = d.update(ciphertext) + d.finalize()

#remove the padding to get the original plaintext
unpadder = padding.PKCS7(128).unpadder()
plaintext = unpadder.update(padded_plaintext) + unpadder.finalize()
print(plaintext)
Sk3pper commented 6 years ago

@DanBeard it does not work. The error is in the penultimate line: plaintext = unpadder.update(padded_plaintext) + unpadder.finalize();

DanBeard commented 6 years ago

Thanks @Sk3pper - misnamed variable. Somehow a semicolon snuck in there and I forgot to import b64encode. I wonder how I got that snippet to work on my machine in the first place!

Vigneshwaran1999 commented 6 years ago

Help for android studio aes-256-cbc

subramanyamVemu commented 3 years ago

I was curious too so I did some digging. The result from a call like: CryptoJS.AES.encrypt('my message', 'secret key 123');

returns an object that has all of the needed information to decrypt the ciphertext (even the key itself! so don't save that object anywhere! )

For example here I can build a lookup object with the key, iv and ciphertext in base64 encoded format:

var ciphertext = CryptoJS.AES.encrypt('my message', 'secret key 123');
console.log(JSON.stringify({key:ciphertext.key.toString(CryptoJS.enc.Base64), iv:ciphertext.iv.toString(CryptoJS.enc.Base64), ciphertext:ciphertext.ciphertext.toString(CryptoJS.enc.Base64)}))

I can then take that object, and get the original plaintext in, for example, python using it's cryptography library:

import os
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
from base64 import b64encode, b64decode

js = {"key":"FseXqPPUgnuscV5t4xajK+TyaIzhCr6ezjKHgeTO7HI=","iv":"JrMAW5tzT9umMrlWSdtSwg==","ciphertext":"3UIxU1zMdAjtAQ4XHltVWA=="}
# base64 decode the buffers for everything
iv = b64decode(js["iv"])
key = b64decode(js["key"])
ciphertext = b64decode(js["ciphertext"])

# construct a decryptor with the IV and key
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
d = cipher.decryptor()

#get the (padded) plaintext out
padded_plaintext = d.update(ciphertext) + d.finalize()

#remove the padding to get the original plaintext
unpadder = padding.PKCS7(128).unpadder()
plaintext = unpadder.update(padded_plaintext) + unpadder.finalize()
print(plaintext)

If someone is interested in the reverse i.e get the cipher text

userinput = b'helloworld'

reconstruct it now

cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()) encryptor=cipher.encryptor() padder = padding.PKCS7(128).padder() padded_data = padder.update(userinput) padded_data += padder.finalize() ct = b64encode(encryptor.update(padded_data)) print(ct)

julydate commented 1 year ago

I see that this article implements the above AES encryption and decryption operation in python. And realize the conversion from password to key and iv in python.

Encryption and decryption of AES algorithm for Crypto-JS and Python

It helps me understand the operation flow that crypto-js uses passwords instead of key and iv to encrypt and decrypt AES. Maybe this codes can help others.

The following code comes from the above article

Use password only

when use CryptoJS:

var text = "123456";
var passphrase = "0123456789asdfgh";  // 十六位字符串作为密钥

var encryptedMessage = CryptoJS.AES.encrypt(text, passphrase).toString();
console.log("encrypt:", encryptedMessage);  
// encrypt: U2FsdGVkX18hyuQnNnZyAe7emBZrUR/YGmy90QN1DI4=

var decryptedMessage = CryptoJS.AES.decrypt(encryptedMessage, passphrase).toString(CryptoJS.enc.Utf8);

console.log("decrypt:", decryptedMessage);
// decrypt: 123456

when use pycryptodome(python3):

import base64
from Crypto.Cipher import AES
from Crypto import Random

def pad(s):
    return s + (16 - len(s) % 16) * chr(16 - len(s) % 16).encode()

def unpad(s):
    return s[0:-ord(s[len(s)-1:])]

def bytes_to_key(data, salt, output=48):
    assert len(salt) == 8, len(salt)
    data += salt
    key = md5(data).digest()
    final_key = key
    while len(final_key) < output:
        key = md5(key + data).digest()
        final_key += key
    return final_key[:output]

def encrypt(data, passphrase):
    salt = Random.new().read(8)
    key_iv = bytes_to_key(passphrase, salt, 32+16)
    key = key_iv[:32]
    iv = key_iv[32:]
    aes = AES.new(key, AES.MODE_CBC, iv)
    cipherbyte = base64.b64encode(b"Salted__" + salt + aes.encrypt(pad(data)))
    return cipherbyte

def decrypt(data, passphrase):
    data = base64.b64decode(data)
    assert data[:8] == b'Salted__'
    salt = data[8:16]
    key_iv = bytes_to_key(passphrase, salt, 32+16)
    key = key_iv[:32]
    iv = key_iv[32:]
    aes = AES.new(key, AES.MODE_CBC, iv)
    plainbyte = unpad(aes.decrypt(data[16:]))
    return plainbyte

if __name__ == '__main__':
    data = b'123456'
    passphrase = b'0123456789asdfgh'

    # encrypt_data = encrypt(data, passphrase)
    # print('encrypt_data:', encrypt_data)

    encrypt_data = b"U2FsdGVkX18hyuQnNnZyAe7emBZrUR/YGmy90QN1DI4="

    decrypt_data = decrypt(encrypt_data, passphrase)
    print('decrypt_data:', decrypt_data)

when use cryptography(python3):

import os
import base64
from hashlib import md5
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

def pad(s):
    return s + (16 - len(s) % 16) * chr(16 - len(s) % 16).encode()

def unpad(s):
    return s[0:-ord(s[len(s)-1:])]

def bytes_to_key(data, salt, output=48):
    assert len(salt) == 8, len(salt)
    data += salt
    key = md5(data).digest()
    final_key = key
    while len(final_key) < output:
        key = md5(key + data).digest()
        final_key += key
    return final_key[:output]

def encrypt(data, passphrase):
    salt = os.urandom(8)
    key_iv = bytes_to_key(passphrase, salt, 32+16)
    key = key_iv[:32]
    iv = key_iv[32:]
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
    encryptor = cipher.encryptor()
    encrypted = encryptor.update(pad(data)) + encryptor.finalize()
    cipherbyte = base64.b64encode(b"Salted__" + salt + encrypted)
    return cipherbyte

def decrypt(data, passphrase):
    data = base64.b64decode(data)
    assert data[:8] == b'Salted__'
    salt = data[8:16]
    key_iv = bytes_to_key(passphrase, salt, 32+16)
    key = key_iv[:32]
    iv = key_iv[32:]
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
    decryptor = cipher.decryptor()
    plainbyte = unpad(decryptor.update(data[16:]) + decryptor.finalize())
    return plainbyte

if __name__ == '__main__':
    data = b'123456'
    passphrase = b'0123456789asdfgh'

    encrypt_data = encrypt(data, passphrase)
    print('encrypt_data:', encrypt_data)

    #encrypt_data = b"U2FsdGVkX18hyuQnNnZyAe7emBZrUR/YGmy90QN1DI4="
    decrypt_data = decrypt(encrypt_data, passphrase)
    print('decrypt_data:', decrypt_data)

Use key and iv

when use CryptoJS:

function Str2Bytes(str) {
    var bytes = [];
    for (var i = 0; i < str.length; i++) {
        var charCode = str.charCodeAt(i);
        bytes.push(charCode);
    }
    return bytes;
}

function Bytes2Str(bytes) {
    var str = "";
    for (var i = 0; i < bytes.length; i++) {
        var char = String.fromCharCode(bytes[i]);
        str = str + char;
    }
    return str;
}

/**
 * 密码 `0` 填充
 * @param {string} key     密码
 * @param {Number} keySize 填充长度, 值: 16,32,64,128
 */
function fillKey(key, keySize) {
    keySize = keySize || 128;
    var buffer = new ArrayBuffer(keySize);
    var view = new Int8Array(buffer);

    var bytes = Str2Bytes(key);
    var keys = Int8Array.from(bytes);
    if (keys.length < view.length) {
        for (var i = 0; i < view.length; i++) {
            if (keys[i] == undefined) {
                view[i] = 48;
            } else {
                view[i] = keys[i];
            }
        }
        return Bytes2Str(view);
    } else {
        return key;
    }
}

function unfillKey(key) {
    var bytes = [];
    var keys = Str2Bytes(key);
    for (var i = 0; i < keys.length; i++) {
        if (keys[i] != 48) {
            bytes.push(keys[i]);
        }
    }
    return Bytes2Str(bytes);
}

var text = "123456";

//var key = fillKey(key, 16);
//var iv = fillKey(iv, 16);

const key = CryptoJS.enc.Utf8.parse("1234123412ASDFGH");  //十六位字符串作为密钥
const iv = CryptoJS.enc.Utf8.parse('ASDFGH1234123412');   //十六位字符串作为偏移量

var encrypted = CryptoJS.AES.encrypt(text, key, {
    iv: iv,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
});

// 转换为字符串
encryptedStr = encrypted.toString();
console.log("encrypted:", encryptedStr);
// encrypted:D6AFFsf6c1De2EsRDEOPBA==

var decrypted = CryptoJS.AES.decrypt(encryptedStr, key, {
    iv: iv,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
});

// 转换为 utf8 字符串
decryptedStr = CryptoJS.enc.Utf8.stringify(decrypted);
console.log("decrypted:", decryptedStr);
// decrypted:123456

when use pycryptodome(python3):

import base64
from Crypto.Cipher import AES
from Crypto import Random

def pad(s):
    return s + (16 - len(s) % 16) * chr(16 - len(s) % 16).encode()

def unpad(s):
    return s[0:-ord(s[len(s)-1:])]

def encrypt(data, key, iv):
    aes = AES.new(key, AES.MODE_CBC, iv)
    cipherbyte = base64.b64encode(aes.encrypt(pad(data)))
    return cipherbyte

def decrypt(data, key, iv):
    data = base64.b64decode(data)
    aes = AES.new(key, AES.MODE_CBC, iv)
    plainbyte = unpad(aes.decrypt(data))
    return plainbyte

if __name__ == '__main__':
    key = b"1234123412ASDFGH"
    iv = b"ASDFGH1234123412"

    data = b"123456"

    # encrypt_data = encrypt(data, key, iv)
    # print("encrypt_data", encrypt_data)

    encrypt_data = b"D6AFFsf6c1De2EsRDEOPBA=="

    decrypt_data = decrypt(encrypt_data, key, iv)
    print('decrypt_data:', decrypt_data)
dantascaio commented 10 months ago

Thanks @julydate! This worked for me!

I see that this article implements the above AES encryption and decryption operation in python. And realize the conversion from password to key and iv in python.

Encryption and decryption of AES algorithm for Crypto-JS and Python

It helps me understand the operation flow that crypto-js uses passwords instead of key and iv to encrypt and decrypt AES. Maybe this codes can help others.

The following code comes from the above article

Use password only

when use CryptoJS:

var text = "123456";
var passphrase = "0123456789asdfgh";  // 十六位字符串作为密钥

var encryptedMessage = CryptoJS.AES.encrypt(text, passphrase).toString();
console.log("encrypt:", encryptedMessage);  
// encrypt: U2FsdGVkX18hyuQnNnZyAe7emBZrUR/YGmy90QN1DI4=

var decryptedMessage = CryptoJS.AES.decrypt(encryptedMessage, passphrase).toString(CryptoJS.enc.Utf8);

console.log("decrypt:", decryptedMessage);
// decrypt: 123456

when use pycryptodome(python3):

import base64
from Crypto.Cipher import AES
from Crypto import Random

def pad(s):
    return s + (16 - len(s) % 16) * chr(16 - len(s) % 16).encode()

def unpad(s):
    return s[0:-ord(s[len(s)-1:])]

def bytes_to_key(data, salt, output=48):
    assert len(salt) == 8, len(salt)
    data += salt
    key = md5(data).digest()
    final_key = key
    while len(final_key) < output:
        key = md5(key + data).digest()
        final_key += key
    return final_key[:output]

def encrypt(data, passphrase):
    salt = Random.new().read(8)
    key_iv = bytes_to_key(passphrase, salt, 32+16)
    key = key_iv[:32]
    iv = key_iv[32:]
    aes = AES.new(key, AES.MODE_CBC, iv)
    cipherbyte = base64.b64encode(b"Salted__" + salt + aes.encrypt(pad(data)))
    return cipherbyte

def decrypt(data, passphrase):
    data = base64.b64decode(data)
    assert data[:8] == b'Salted__'
    salt = data[8:16]
    key_iv = bytes_to_key(passphrase, salt, 32+16)
    key = key_iv[:32]
    iv = key_iv[32:]
    aes = AES.new(key, AES.MODE_CBC, iv)
    plainbyte = unpad(aes.decrypt(data[16:]))
    return plainbyte

if __name__ == '__main__':
    data = b'123456'
    passphrase = b'0123456789asdfgh'

    # encrypt_data = encrypt(data, passphrase)
    # print('encrypt_data:', encrypt_data)

    encrypt_data = b"U2FsdGVkX18hyuQnNnZyAe7emBZrUR/YGmy90QN1DI4="

    decrypt_data = decrypt(encrypt_data, passphrase)
    print('decrypt_data:', decrypt_data)

when use cryptography(python3):

import os
import base64
from hashlib import md5
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

def pad(s):
    return s + (16 - len(s) % 16) * chr(16 - len(s) % 16).encode()

def unpad(s):
    return s[0:-ord(s[len(s)-1:])]

def bytes_to_key(data, salt, output=48):
    assert len(salt) == 8, len(salt)
    data += salt
    key = md5(data).digest()
    final_key = key
    while len(final_key) < output:
        key = md5(key + data).digest()
        final_key += key
    return final_key[:output]

def encrypt(data, passphrase):
    salt = os.urandom(8)
    key_iv = bytes_to_key(passphrase, salt, 32+16)
    key = key_iv[:32]
    iv = key_iv[32:]
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
    encryptor = cipher.encryptor()
    encrypted = encryptor.update(pad(data)) + encryptor.finalize()
    cipherbyte = base64.b64encode(b"Salted__" + salt + encrypted)
    return cipherbyte

def decrypt(data, passphrase):
    data = base64.b64decode(data)
    assert data[:8] == b'Salted__'
    salt = data[8:16]
    key_iv = bytes_to_key(passphrase, salt, 32+16)
    key = key_iv[:32]
    iv = key_iv[32:]
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
    decryptor = cipher.decryptor()
    plainbyte = unpad(decryptor.update(data[16:]) + decryptor.finalize())
    return plainbyte

if __name__ == '__main__':
    data = b'123456'
    passphrase = b'0123456789asdfgh'

    encrypt_data = encrypt(data, passphrase)
    print('encrypt_data:', encrypt_data)

    #encrypt_data = b"U2FsdGVkX18hyuQnNnZyAe7emBZrUR/YGmy90QN1DI4="
    decrypt_data = decrypt(encrypt_data, passphrase)
    print('decrypt_data:', decrypt_data)

Use key and iv

when use CryptoJS:

function Str2Bytes(str) {
    var bytes = [];
    for (var i = 0; i < str.length; i++) {
        var charCode = str.charCodeAt(i);
        bytes.push(charCode);
    }
    return bytes;
}

function Bytes2Str(bytes) {
    var str = "";
    for (var i = 0; i < bytes.length; i++) {
        var char = String.fromCharCode(bytes[i]);
        str = str + char;
    }
    return str;
}

/**
 * 密码 `0` 填充
 * @param {string} key     密码
 * @param {Number} keySize 填充长度, 值: 16,32,64,128
 */
function fillKey(key, keySize) {
    keySize = keySize || 128;
    var buffer = new ArrayBuffer(keySize);
    var view = new Int8Array(buffer);

    var bytes = Str2Bytes(key);
    var keys = Int8Array.from(bytes);
    if (keys.length < view.length) {
        for (var i = 0; i < view.length; i++) {
            if (keys[i] == undefined) {
                view[i] = 48;
            } else {
                view[i] = keys[i];
            }
        }
        return Bytes2Str(view);
    } else {
        return key;
    }
}

function unfillKey(key) {
    var bytes = [];
    var keys = Str2Bytes(key);
    for (var i = 0; i < keys.length; i++) {
        if (keys[i] != 48) {
            bytes.push(keys[i]);
        }
    }
    return Bytes2Str(bytes);
}

var text = "123456";

//var key = fillKey(key, 16);
//var iv = fillKey(iv, 16);

const key = CryptoJS.enc.Utf8.parse("1234123412ASDFGH");  //十六位字符串作为密钥
const iv = CryptoJS.enc.Utf8.parse('ASDFGH1234123412');   //十六位字符串作为偏移量

var encrypted = CryptoJS.AES.encrypt(text, key, {
    iv: iv,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
});

// 转换为字符串
encryptedStr = encrypted.toString();
console.log("encrypted:", encryptedStr);
// encrypted:D6AFFsf6c1De2EsRDEOPBA==

var decrypted = CryptoJS.AES.decrypt(encryptedStr, key, {
    iv: iv,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
});

// 转换为 utf8 字符串
decryptedStr = CryptoJS.enc.Utf8.stringify(decrypted);
console.log("decrypted:", decryptedStr);
// decrypted:123456

when use pycryptodome(python3):

import base64
from Crypto.Cipher import AES
from Crypto import Random

def pad(s):
    return s + (16 - len(s) % 16) * chr(16 - len(s) % 16).encode()

def unpad(s):
    return s[0:-ord(s[len(s)-1:])]

def encrypt(data, key, iv):
    aes = AES.new(key, AES.MODE_CBC, iv)
    cipherbyte = base64.b64encode(aes.encrypt(pad(data)))
    return cipherbyte

def decrypt(data, key, iv):
    data = base64.b64decode(data)
    aes = AES.new(key, AES.MODE_CBC, iv)
    plainbyte = unpad(aes.decrypt(data))
    return plainbyte

if __name__ == '__main__':
    key = b"1234123412ASDFGH"
    iv = b"ASDFGH1234123412"

    data = b"123456"

    # encrypt_data = encrypt(data, key, iv)
    # print("encrypt_data", encrypt_data)

    encrypt_data = b"D6AFFsf6c1De2EsRDEOPBA=="

    decrypt_data = decrypt(encrypt_data, key, iv)
    print('decrypt_data:', decrypt_data)
orthlus commented 2 months ago

I done some implementation on java https://github.com/orthlus/java-cryptojs-helper Maybe helpful