等效于 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