Java AES 和使用我自己的密钥
Posted
技术标签:
【中文标题】Java AES 和使用我自己的密钥【英文标题】:Java AES and using my own Key 【发布时间】:2011-03-27 23:18:45 【问题描述】:我想用我自己的密钥使用 AES 加密一个字符串。但是我在密钥的位长度上遇到了问题。您能否查看我的代码并查看我需要修复/更改的内容。
public static void main(String[] args) throws Exception
String username = "bob@google.org";
String password = "Password1";
String secretID = "BlahBlahBlah";
String SALT2 = "deliciously salty";
// Get the Key
byte[] key = (SALT2 + username + password).getBytes();
System.out.println((SALT2 + username + password).getBytes().length);
// Need to pad key for AES
// TODO: Best way?
// Generate the secret key specs.
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
// Instantiate the cipher
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] encrypted = cipher.doFinal((secrectID).getBytes());
System.out.println("encrypted string: " + asHex(encrypted));
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
byte[] original = cipher.doFinal(encrypted);
String originalString = new String(original);
System.out.println("Original string: " + originalString + "\nOriginal string (Hex): " + asHex(original));
现在我收到异常“无效的 AES 密钥长度:86 字节”。我需要垫我的钥匙吗?我该怎么做?
我还需要为 ECB 或 CBC 设置什么吗?
谢谢
【问题讨论】:
I find your lack of random salt disturbing。现在认真:在密码学的背景下SALT should be random 哈哈,好笑。实际上我确实有一个随机盐,但我清理了我的代码以使我的问题更清楚。这就是变量命名为 SALT2 的原因。但对于遇到同样问题并喜欢复制/粘贴代码的其他人来说,这是一个很好的参考。 【参考方案1】:编辑:
正如 cmets 中所写,旧代码不是“最佳实践”。 您应该使用具有高迭代次数的密钥生成算法,例如 PBKDF2。 您还应该至少部分使用非静态(意味着每个“身份”专有)盐。如果可能的话随机生成并与密文一起存储。
SecureRandom sr = SecureRandom.getInstanceStrong();
byte[] salt = new byte[16];
sr.nextBytes(salt);
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 1000, 128 * 8);
SecretKey key = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(spec);
Cipher aes = Cipher.getInstance("AES");
aes.init(Cipher.ENCRYPT_MODE, key);
============
旧答案
您应该使用 SHA-1 从您的密钥生成散列,并将结果修剪为 128 位(16 字节)。
另外,不要通过 getBytes() 从字符串生成字节数组,它使用平台默认字符集。所以密码“blaöä”在不同的平台上会产生不同的字节数组。
byte[] key = (SALT2 + username + password).getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key, 16); // use only first 128 bit
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
编辑: 如果您需要 256 位作为密钥大小,您需要下载“Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files”Oracle download link,使用 SHA-256 作为哈希并删除 Arrays.copyOf 行. “ECB”是默认密码模式,“PKCS5Padding”是默认填充。 您可以使用以下格式通过 Cipher.getInstance 字符串使用不同的密码模式和填充模式:“密码/模式/填充”
对于使用 CTS 和 PKCS5Padding 的 AES,字符串为:“AES/CTS/PKCS5Padding”
【讨论】:
这会起作用,但它会散列我的密码,然后只使用前几位。没有更好的方法吗? 没有更好的方法来生成密钥,因为 AES 需要 128/192/256 位密钥。如果你不散列你的密钥并且只修剪输入,它只会使用前 16/24/32 字节。所以生成一个Hash是唯一合理的方式。 请注意,此答案没有使用良好的密钥派生函数,因此 不如应有的安全。请参阅other answer 了解稍微过时的密钥派生函数 - 不幸的是仍然是静态盐。 我可以建议删除这个答案,因为它是非常不好的做法。应该使用适当的密钥派生函数 - 至少 PBKDF2。 是的,正如 Maarten 多年前所说,答案非常糟糕。请检查来自Cryptography 和Key Derivation Function 的答案【参考方案2】:您应该使用 KeyGenerator 来生成密钥,
AES 密钥长度为 128、192 和 256 位,具体取决于您要使用的密码。
看教程here
这是基于密码的加密的代码,这是通过 System.in 输入的密码,如果需要,您可以将其更改为使用存储的密码。
PBEKeySpec pbeKeySpec;
PBEParameterSpec pbeParamSpec;
SecretKeyFactory keyFac;
// Salt
byte[] salt =
(byte)0xc7, (byte)0x73, (byte)0x21, (byte)0x8c,
(byte)0x7e, (byte)0xc8, (byte)0xee, (byte)0x99
;
// Iteration count
int count = 20;
// Create PBE parameter set
pbeParamSpec = new PBEParameterSpec(salt, count);
// Prompt user for encryption password.
// Collect user password as char array (using the
// "readPassword" method from above), and convert
// it into a SecretKey object, using a PBE key
// factory.
System.out.print("Enter encryption password: ");
System.out.flush();
pbeKeySpec = new PBEKeySpec(readPassword(System.in));
keyFac = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);
// Create PBE Cipher
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
// Initialize PBE Cipher with key and parameters
pbeCipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);
// Our cleartext
byte[] cleartext = "This is another example".getBytes();
// Encrypt the cleartext
byte[] ciphertext = pbeCipher.doFinal(cleartext);
【讨论】:
如何使用 KeyGenerator 生成带有密码的密钥?我想根据密码生成相同的密钥。所以我可以稍后解密字符串。 您所说的是基于密码的加密,而不是 AES。我用 PBE 的示例程序更新了我的答案 尝试使用 PBEKDF2 密钥生成器,对SecretKeyFactory
使用字符串“PBKDF2WithHmacSHA1”以获得更多最新加密。
实际上此答案中所有使用的加密原语都已过时,MD5 和 DES 肯定是。注意。
MD5 和 DES 是弱密码套件,应避免使用【参考方案3】:
import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.*;
import java.io.BufferedReader;
import java.io.FileReader;
public class AESFile
private static String algorithm = "AES";
private static byte[] keyValue=new byte[] '0','2','3','4','5','6','7','8','9','1','2','3','4','5','6','7';// your key
// Performs Encryption
public static String encrypt(String plainText) throws Exception
Key key = generateKey();
Cipher chiper = Cipher.getInstance(algorithm);
chiper.init(Cipher.ENCRYPT_MODE, key);
byte[] encVal = chiper.doFinal(plainText.getBytes());
String encryptedValue = new BASE64Encoder().encode(encVal);
return encryptedValue;
// Performs decryption
public static String decrypt(String encryptedText) throws Exception
// generate key
Key key = generateKey();
Cipher chiper = Cipher.getInstance(algorithm);
chiper.init(Cipher.DECRYPT_MODE, key);
byte[] decordedValue = new BASE64Decoder().decodeBuffer(encryptedText);
byte[] decValue = chiper.doFinal(decordedValue);
String decryptedValue = new String(decValue);
return decryptedValue;
//generateKey() is used to generate a secret key for AES algorithm
private static Key generateKey() throws Exception
Key key = new SecretKeySpec(keyValue, algorithm);
return key;
// performs encryption & decryption
public static void main(String[] args) throws Exception
FileReader file = new FileReader("C://myprograms//plaintext.txt");
BufferedReader reader = new BufferedReader(file);
String text = "";
String line = reader.readLine();
while(line!= null)
text += line;
line = reader.readLine();
reader.close();
System.out.println(text);
String plainText = text;
String encryptedText = AESFile.encrypt(plainText);
String decryptedText = AESFile.decrypt(encryptedText);
System.out.println("Plain Text : " + plainText);
System.out.println("Encrypted Text : " + encryptedText);
System.out.println("Decrypted Text : " + decryptedText);
【讨论】:
可能会添加更多解释文本。 问题,在字节数组中使用keyValue
有什么意义?我看到它被用来制作钥匙,为什么?可以使用 SecretKey
来代替吗?如果有,怎么做?
@Mandrek,文件“plaintext.txt”的内容将被加密。上述逻辑加密文件中的数据/消息,在 FileReader 构造函数中作为参数读取。【参考方案4】:
这会起作用的。
public class CryptoUtils
private final String TRANSFORMATION = "AES";
private final String encodekey = "1234543444555666";
public String encrypt(String inputFile)
throws CryptoException
return doEncrypt(encodekey, inputFile);
public String decrypt(String input)
throws CryptoException
// return doCrypto(Cipher.DECRYPT_MODE, key, inputFile);
return doDecrypt(encodekey,input);
private String doEncrypt(String encodekey, String inputStr) throws CryptoException
try
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
byte[] key = encodekey.getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key, 16); // use only first 128 bit
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] inputBytes = inputStr.getBytes();
byte[] outputBytes = cipher.doFinal(inputBytes);
return Base64Utils.encodeToString(outputBytes);
catch (NoSuchPaddingException | NoSuchAlgorithmException
| InvalidKeyException | BadPaddingException
| IllegalBlockSizeException | IOException ex)
throw new CryptoException("Error encrypting/decrypting file", ex);
public String doDecrypt(String encodekey,String encrptedStr)
try
Cipher dcipher = Cipher.getInstance(TRANSFORMATION);
dcipher = Cipher.getInstance("AES");
byte[] key = encodekey.getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key, 16); // use only first 128 bit
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
dcipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
// decode with base64 to get bytes
byte[] dec = Base64Utils.decode(encrptedStr.getBytes());
byte[] utf8 = dcipher.doFinal(dec);
// create new string based on the specified charset
return new String(utf8, "UTF8");
catch (Exception e)
e.printStackTrace();
return null;
【讨论】:
【参考方案5】:MD5、AES、无填充
import static javax.crypto.Cipher.DECRYPT_MODE;
import static javax.crypto.Cipher.ENCRYPT_MODE;
import static org.apache.commons.io.Charsets.UTF_8;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
public class PasswordUtils
private PasswordUtils()
public static String encrypt(String text, String pass)
try
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
Key key = new SecretKeySpec(messageDigest.digest(pass.getBytes(UTF_8)), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(ENCRYPT_MODE, key);
byte[] encrypted = cipher.doFinal(text.getBytes(UTF_8));
byte[] encoded = Base64.getEncoder().encode(encrypted);
return new String(encoded, UTF_8);
catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e)
throw new RuntimeException("Cannot encrypt", e);
public static String decrypt(String text, String pass)
try
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
Key key = new SecretKeySpec(messageDigest.digest(pass.getBytes(UTF_8)), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(DECRYPT_MODE, key);
byte[] decoded = Base64.getDecoder().decode(text.getBytes(UTF_8));
byte[] decrypted = cipher.doFinal(decoded);
return new String(decrypted, UTF_8);
catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e)
throw new RuntimeException("Cannot decrypt", e);
【讨论】:
如何在角度(离子 4)中创建像 SecretKeySpec 这样的安全密钥;【参考方案6】: byte[] seed = (SALT2 + username + password).getBytes();
SecureRandom random = new SecureRandom(seed);
KeyGenerator generator;
generator = KeyGenerator.getInstance("AES");
generator.init(random);
generator.init(256);
Key keyObj = generator.generateKey();
【讨论】:
以上是关于Java AES 和使用我自己的密钥的主要内容,如果未能解决你的问题,请参考以下文章