等效于 MySQL aes_encrypt() 函数的 AES 加密方法

Posted

技术标签:

【中文标题】等效于 MySQL aes_encrypt() 函数的 AES 加密方法【英文标题】:AES encryption method equivalent to MySQL aes_encrypt() function 【发布时间】:2021-11-23 00:21:39 【问题描述】:

我想写一个AES加密方法,应该相当于mysqlaes_encrypt

我试着写,但它不正确,因为 mysql 也没有给出正确的数据。

我应该怎么做才能让它正确?

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);

【问题讨论】:

请先发布您当前不起作用的代码。 密码密码 = Cipher.getInstance("AES");cipher.init(Cipher.ENCRYPT_MODE, keySpec); byte[] encryptedTextBytes = cipher.doFinal(message .getBytes("UTF-8"));字符串 k = 新字符串(加密文本字节); System.out.println("KKKKK"+k); @user2888996 将来编辑您的问题以添加其他详细信息,例如代码。这次我给你加了。 谢谢,我是新来的,不知道如何正确发帖。以后我会记得的 String k = new String(encryptedTextBytes) 不正确。 AES 密码的输出不是字符数据(理想情况下,它与随机数据无法区分)并且不太可能表示有效的 UTF-8 代码点。如果您想将密文存储为String,您应该将字节数组编码为 Base64 或 Hex。 【参考方案1】:

MySQL 的 AES 实现让很多人头疼。这主要是因为 MySQL 如何处理加密密钥。加密密钥被分成 16 字节的块,MySQL 会将一个块中的字节与前一个块中的字节进行异或。如果用户提供的密钥的长度恰好小于 16 字节,则密钥本质上会用空字节填充以达到 16 字节。这就是 MySQL 的 aes_encrypt() 处理密钥的方式。

还处理要加密的值,通过使用 PKCS7 填充数据。您可以了解有关 PKCS7 @http://en.wikipedia.org/wiki/Padding_%28cryptography%29#PKCS7 的所有信息,但它所做的只是填充输入数据,使其位于 16 字节块中。数据填充的字节等于将添加的填充字节数。

长话短说,您需要像 MySQL 一样处理加密密钥,并使用 PKCS7 填充您的输入数据。

有关 Java 中的示例代码,请参阅 Michael Simmons 的以下帖子: http://info.michael-simons.eu/2011/07/18/mysql-compatible-aes-encryption-decryption-in-java/

【讨论】:

【参考方案2】:

几年前我不得不使用 BouncyCastle 来做这件事。正如 Alen Puzic 的回答中所述,这两个问题是 mysql 密钥生成和 PKCS7 填充。 BouncyCastle 将使用他们的PaddedBufferedBlockCipher 为您处理填充,但您需要自己生成密钥。这是执行此操作的代码:

/**
 * 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);

注意mysql的默认keyLength是128。

使用上面的方法生成KeyParameter就可以完成如下的加解密。

/**
 * 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;

【讨论】:

【参考方案3】:

感谢指点,终于搞定了,下面是一个简单的python版本:

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'))

【讨论】:

【参考方案4】:

我建议使用the Bouncy Castle Java 加密 API。 BC 被广泛认为是一个很棒的加密工具包,如果你愿意,它可以作为加密提供者插入到 Java API 中。我知道这并不能直接回答您的问题,但我从未见过有人对 Bouncy Castle 有任何疑问。

【讨论】:

抱歉,我必须对此投反对票。正如您所承认的,这不是问题的答案。我确信一旦发现问题就可以使用 BouncyCastle,但对于许多 JCE 提供者(包括 Oracle 提供者......)可能也是如此。

以上是关于等效于 MySQL aes_encrypt() 函数的 AES 加密方法的主要内容,如果未能解决你的问题,请参考以下文章

MYSQL SELECT WHERE LIKE WITH AES_ENCRYPT

MySql AES_DECRYPT & AES_ENCRYPT 密钥在 PHP 中不起作用

技术分享 | 详解SQL加密函数:AES_ENCRYPT()

MySQL MATCH() AGAINST() 等效于 SQL Server

Java 等效于 PHP 的 mysql_real_escape_string()

等效于 SQL SERVER 的 MySQL LIMIT 子句