简体   繁体   中英

AES encryption method equivalent to MySQL aes_encrypt() function

I want to write an AES encryption method that should be equivalent to mysql aes_encrypt .

I try to write but it's not correct, because mysql is nor giving proper data.

What should I do to get it correct?

Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec); 
byte[] encryptedTextBytes = cipher.doFinal(message .getBytes("UTF-8")); 
String k = new String(encryptedTextBytes); 
System.out.println("KKKKK"+k);

MySQL's implementation of AES gives headaches to a lot of people. It'a mainly because of how MySQL processes the encryption key. The encryption key gets broken into 16-byte blocks and MySQL will XOR the bytes from one block with the bytes in the preceding block. If the user provided key happens to be less than 16 bytes in length then the key is essentially padded with null bytes to get up to 16 bytes. That's how the key is processed by MySQL's aes_encrypt().

The value which is to be encrypted is also processed, by padding the data using PKCS7. You can learn all about PKCS7 @ http://en.wikipedia.org/wiki/Padding_%28cryptography%29#PKCS7 but all it does is pad the input data so it's in 16-byte blocks. The byte the data gets padded with is equivalent to the number of padding bytes that will be added.

Long story short you need process the encryption key the way MySQL does and also pad your input data using PKCS7.

Refer to the following post by Michael Simmons for example code in Java: http://info.michael-simons.eu/2011/07/18/mysql-compatible-aes-encryption-decryption-in-java/

I had to do this several years ago using BouncyCastle. As stated in Alen Puzic's answer the two issues are the mysql key generation and the PKCS7 padding. BouncyCastle will handle the padding for you with their PaddedBufferedBlockCipher but you will need to generate the key yourself. Here is the code to do that:

/**
 * Use password to generate a MySQL AES symmetric key
 * @param passwd Password String to use.
 * @param keyLength Must be evenly divisible by 8.
 * @return Key for use with MySQL AES encrypt/decrypt fuctions.
 */
public static KeyParameter getMySqlAESPasswdKey(String passwd, int keyLength) {
    byte[] pword = passwd.getBytes();
    byte[] rawKey = new byte[keyLength/8];
    int j = 0;
    for (int i = 0; i < pword.length; i++, j++) {

        if(j==rawKey.length) {
            j = 0;
        }
        rawKey[j] = pword[i];
    }

    return new KeyParameter(rawKey);
}

Note that the default keyLength for mysql is 128.

Using the above method to generate the KeyParameter you can complete an encryption/decryption with the following.

/**
 * Password based encryption using AES with MySql style key generation.
 * @param toEncrypt Unencrypted byte array.
 * @param key A KeyParameter generated with the getMySqlAESPasswdKey() method.
 * @return Encrypted byte array.
 * @throws InvalidCipherTextException If provided key cannot be used with this method on the provided data.
 */
public static byte[] mysqlAesPasswdEncrypt (byte [] toEncrypt, KeyParameter key) throws InvalidCipherTextException {
    BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new AESFastEngine());

    cipher.init(true, key);
    byte[] result = new byte[cipher.getOutputSize(toEncrypt.length)];
    int len = cipher.processBytes(toEncrypt, 0, toEncrypt.length, result, 0);
    cipher.doFinal(result, len);
    return result;
}

/**
 * Password based decryption using AES with MySql style key generation.
 * @param toDecrypt Encrypted byte array.
 * @param key A KeyParameter generated with the getMySqlAESPasswdKey() method.
 * @return Unencrypted byte array.
 * @throws InvalidCipherTextException If provided key cannot be used with this method on the provided data.
 */
public static byte[] mysqlAesPasswdDecrypt (byte [] toDecrypt, KeyParameter key) throws InvalidCipherTextException {
    BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new AESFastEngine());

    cipher.init(false, key);
    byte[] result = new byte[cipher.getOutputSize(toDecrypt.length)];
    int len = cipher.processBytes(toDecrypt, 0, toDecrypt.length, result, 0);
    cipher.doFinal(result, len);
    return stripTrailingZeros(result);
}

/**
 * Strip trailling zeros from the end of decrypted byte arrays.
 * @param data Data to strip.
 * @return Stripped data.
 */
public static byte[] stripTrailingZeros(byte[] data) {
    int lastData = data.length-1;
    for (int i = data.length-1; i >= 0; i--) {
        if(data[i]!=(byte)0) {
            lastData = i;
            break;
        }
    }

    byte[] data2 = new byte[lastData+1];
    System.arraycopy(data, 0, data2, 0, lastData+1);
    return data2;
}

Thanks for the insight, finally get it right, here's a simple python version:

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
import codecs


def encrypt(data, key):
    key = key + b"\0" * (16 - len(key) % 16)
    padder = padding.PKCS7(128).padder()
    data = padder.update(data) + padder.finalize()
    alg = algorithms.AES(key)
    cipher = Cipher(alg, modes.ECB(), default_backend())
    encryptor = cipher.encryptor()
    ct = encryptor.update(data) + encryptor.finalize()
    return ct


if __name__ == '__main__':
    enc = encrypt(b'123456', b'1234567890')
    print(codecs.encode(enc, 'hex'))

I would recommend using the Bouncy Castle Java crypto APIs. BC is widely regarded as a great crypto toolkit and it can plugin to the Java API as a crypto provider if you want. I know that doesn't directly answer your question, but I've never seen anyone have problems with Bouncy Castle.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM