AES将任何java对象加密为base64字符串
我试图使用Cipher类将任何java对象(在本例中为整数,但Date也可以)加密为base64字符串。 基本上,我使用ByteArrayOutputStream将给定对象转换为字节数组,并使用密码对该字节数组进行加密。见下文AES将任何java对象加密为base64字符串,java,arrays,encryption,aes,encryption-symmetric,Java,Arrays,Encryption,Aes,Encryption Symmetric,我试图使用Cipher类将任何java对象(在本例中为整数,但Date也可以)加密为base64字符串。 基本上,我使用ByteArrayOutputStream将给定对象转换为字节数组,并使用密码对该字节数组进行加密。见下文 for (Integer i = 0; i < 10; i++) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutput oos = new ObjectOu
for (Integer i = 0; i < 10; i++) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutput oos = new ObjectOutputStream(bos);
oos.writeObject(i);
oos.flush();
byte[] data = bos.toByteArray();
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec("&E(H+MbQeThWmZq4".getBytes("UTF-8"), "AES"));
String base64output = Base64.getEncoder().encodeToString(cipher.doFinal(data));
System.out.println(i + " - " + base64output);
}
对我来说似乎很奇怪,因为相同的前缀BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/hfx9lgpdjc0ilxw8fyd1hfb54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa9
对于每个加密对象。在本例中,我对每个对象使用相同的键,但这不应该是出现此问题的原因
我还用字符串和日期而不是整数测试了这个示例。将日期编码成字节数组并用同样的方法加密也会导致
在这个问题上,所有日期对象都有一个相同的前缀,而使用相同的方法对字符串进行编码似乎效果很好。每一个都经过编码和加密
字符串导致另一个加密的base64字符串。见下文
for (Integer i = 0; i < 10; i++) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutput oos = new ObjectOutputStream(bos);
oos.writeObject(i);
oos.flush();
byte[] data = bos.toByteArray();
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec("&E(H+MbQeThWmZq4".getBytes("UTF-8"), "AES"));
String base64output = Base64.getEncoder().encodeToString(cipher.doFinal(data));
System.out.println(i + " - " + base64output);
}
加密日期的输出:(也具有相同的前缀)
0-CPQXMKQW7MHCksxxSymmjTrpnfgujbjylivkehgm2JRJ1HRBSAIOQHBM2UZI2R0
1-cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JQ0q0kophfAfiPxe0U+sb1R
2-cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JTeTKnbYsLo6TjfuQF9PYIk
3-cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JSrDPGtepg4HWUL6VeBtWg7
4-cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JS7dlSsNjnY011F2BooNnKW
5-cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JStO2xPQvT76/k+xMdaDBpQ
6-CPQxMkQW7MHCksxxSymmJTRPNFGUJBJYLIVKEHGM2JQZ4J3YO8G9TAHI7B/Zefl
7-cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JR8/fOAiuGM8tO8zMcju4Xk
8-cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JSMDHi6UyD5QQY1jRXNCErc
9-cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JRfKstfsC8dPYuPfd9f2B+B
加密字符串的输出:(按预期工作)
0-TNpI3oLRzH5id6c/yRJlQQ==
1-yMkm+ZuYWs4EnISo56Zljw==
2-03i1Lv01Nn2sGDGmtpRAIg==
3-5skvWbkcVXfT2TScaGxNfQ==
4-0p9qg5U+DqAnCBdyji+L9Q==
5-gD5xPtAMy34xC90hKCQeWA==
6-OQWKUUXC5X/f6U9G9la8Q==
7-72cvCiLks3DDaTLAQvoVfw==
8-wQu7Ug5RHg5egbNTI0YXQw==
9-x1BQVwy3r6MP3SDLl/mktw==
有什么想法吗
编辑:
即使我使用CBC或其他加密方法,如DES或Blowfish,也会出现同样的问题。我希望ByteArrayOutputStream中的每个字节数组都应该加密为完全不同的base64字符串,即使它们的前缀相同,长度约为90%。正如Mark指出的,
ObjectOutputStream
创建了一个对象头,因此,公共前缀是因为和您没有使用salt和您使用的是相同的加密密钥
这些弱点使得加密解决方案(即您的代码)容易受到攻击,即使算法本身非常好。您刚刚以一种不安全的方式实现了它。在加密之前使用对象序列化不是一个好主意。要么加密数据以进行传输保护,在这种情况下,TLS更有意义。或者您正在加密较长时间的存储,在这种情况下,序列化是危险的,因为序列化格式可能会更改。见鬼,您可能希望在将来更改整个语言/运行时 我建议你制定自己的协议。例如,在这种情况下,您可以使用或将整数编码为4字节。这样,整数的最小值为4字节(作为无符号32位big-endian值)。对于非常复杂的方法,您甚至可以查看ASN.1结构和编码(在BouncyCastle和其他库中实现) Java日期实际上只是内部的
长,可以完美地存储在8个字节中。另一个选项是将其编码为(UTC)日期字符串,并使用US ASCII兼容编码(StandardCharsets.US_ASCII
)存储该字符串
请注意,欧洲央行模式非常危险。例如,假设0x00FFFFFF
以上的值不常见,并且您不想泄露这些值的存在。还可以设想,最重要的字节是块的最后一个字节,而不是头字节。在这种情况下,很容易区分具有例如0x01
的块和具有0x00
的块,这在这种情况下应该更常见。所以你立即泄露了你的明文信息
如果使用静态IV而不是随机(或至少完全不可预测)IV值,则在CBC模式下,此问题同样突出。您必须为每个CBC加密使用随机IV以确保安全。您可以将IV与密文一起存储。对于CBC来说,16字节IV通常只是在密文前加上前缀。但是,您最好使用具有12字节随机nonce的认证GCM模式
有点遗憾的是,Java完全允许重用密码实例——例如,它不允许密码
在使用后销毁密钥材料。它默认为一种不安全的模式,在这种模式下,静脉注射是重复的,这是非常可耻的。你得自己处理静脉注射问题
使用GCM和ByteBuffer的示例:
public static void main(String[] args) throws Exception {
// input, a date and message
Date date = new Date();
String message = "hello world";
// AES-128 key (replace by a real 256 bit key in your case)
SecretKey aesKey = new SecretKeySpec(new byte[128 / Byte.SIZE], "AES");
// default nonce sizes for GCM, using a constant should be preferred
int nonceSize = 96;
int tagSize = 128;
String cts;
try (StringWriter stringWriter = new StringWriter(); PrintWriter out = new PrintWriter(stringWriter)) {
for (Integer i = 0; i < 10; i++) {
byte[] randomNonce = createRandomIV(nonceSize);
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, randomNonce);
byte[] encodedMessage = message.getBytes(StandardCharsets.UTF_8);
ByteBuffer encodedNumberDateAndMessage = ByteBuffer.allocate(Integer.BYTES + Long.BYTES + encodedMessage.length);
encodedNumberDateAndMessage.putInt(i);
encodedNumberDateAndMessage.putLong(date.getTime());
encodedNumberDateAndMessage.put(encodedMessage);
// for reading we need to flip the buffer
encodedNumberDateAndMessage.flip();
ByteBuffer encryptedNumberDateAndMessage =
ByteBuffer.allocate(nonceSize / Byte.SIZE + encodedNumberDateAndMessage.limit() + tagSize / Byte.SIZE);
encryptedNumberDateAndMessage.put(randomNonce);
Cipher gcm = Cipher.getInstance("AES/GCM/NoPadding");
gcm.init(Cipher.ENCRYPT_MODE, aesKey, gcmSpec);
gcm.doFinal(encodedNumberDateAndMessage, encryptedNumberDateAndMessage);
// not required, we'll be using array() method
// encryptedNumberDateAndMessage.flip();
// we can use the full array as there
String base64Ciphertext = Base64.getEncoder().encodeToString(encryptedNumberDateAndMessage.array());
if (i != 0) {
out.write('\n');
}
out.write(base64Ciphertext);
}
cts = stringWriter.toString();
}
System.out.println(cts);
// TODO decrypt ciphertexts in cts
// hint use BufferedReader to read lines and don't forget to strip off the IV/Nonce first
}
private static byte[] createRandomIV(int sizeInBits) {
if (sizeInBits % Byte.SIZE != 0) {
throw new IllegalArgumentException("Invalid IV size, must be a multiple of 8 bits");
}
byte[] randomNonce = new byte[sizeInBits / Byte.SIZE];
SecureRandom rbg = new SecureRandom();
rbg.nextBytes(randomNonce);
return randomNonce;
}
它由nonce、整数的密文、表示日期的长值和“hello world”字符串以及最终的身份验证标记组成,在Java中,它被认为是密文的一部分。您看到的行为是使用无模式ECB
模式与明文中的相似性相结合的结果。所有分组密码(AES、Blowfish、DES)都会有相同的问题
当使用CBC
时,如果您根据需要提供IVs,所有这些都会消失:
顺便说一句您使用的是一个16字节的密钥,得到的是AES-128而不是AES-256。序列化的整数看起来太长了。尝试检查序列化整数的字节数组(data
)。我从来没做过,但我知道
0 - jt3Mk13pGjeaFf1oNq4LfmQ4z/31nRG4KtZ4H3RK6k/GA1anC3/lrzSXoLsQ6jMsVEpnxU13wAu6lkZJ3it1Ei4i4EsNFixc+YX4K6cIIv4ByY5Q246jd3H0m11C2FZJ
1 - Jqd0RB6lOITqifAaWluW6jx8F8gY4btZHx12CiXtZjfnehhtk64jva4eGTQd4EpvB/5Q/ORhZCNgF3ue0/Na1R9MCsK+mULAcyANdNcLyKbXo272G21z0LPCeweXdjhu
2 - xHdCG6rWNDyLTl8zruo8u+45V/RMXkrB7K+QU5r9lpc3FzvDwpl0wmy9Yj3FOyjMulmVT1zahH+wWVrmB9gNcXy7sGyCH/anJANC396OcDyQXqNIyvOPw9mpUmmRQcwR
3 - ygIDkLtQTupkbB35SzRflE3RAMmdYGSkdGZgRctFHdZCqGt+Arb3RbvhoAiiE9PwkyLmifyllQTTSutvV/ZtlGaGMX3v4bQUZDoaSyXQd9xn+pUSJk87NDVGi37xWw1O
4 - cJYSthCHHGeCqnuBJY8YdUbptKD3XNb2nt+pyIc94vRvjquYf7atu0+bDndFnePWvrlPzFIFXVB8CuANIsDhzRSNEOOU/wOkwcAN2AdavCqlZqN0Mtqdg4vqKGWx2oAE
5 - f7/gu8fJ8jkyhRAXJkLqdnJMLjCfFSjq8ovjhlNcuDPk8N/mYlA2845PGgi74Kb/zCG1WH8NtFK06xrpn15KyUxSANxoQ6C9QnzE9sc4aZj5rUatWeekvBfbqngq3JpG
6 - PitP2MuX4/Yysso8dCl1h2VK3MKoU2YpyzvLgZ3hZX/cBzSWp9O0Eafzj6GIMvAGVaL0x0V+K2Wv4eBOLIhDczhJXvHmKvTU7ZJnAwI37JXkOecN4HJdAXfFqg2WkT5f
7 - 1Mj8WnSqgLE08qfeYC1a3nZQ1jszxbT9J+ClUy8rCYusZHiArQcCgCwrNbWbI2yVfRjYOpsuTgyq31fnuHrkVfGu6RhiRhucR0a0Dign5fSU71STKksweHQ+oYQJibnQ
8 - TgGDGlOFWyfKO50xxPTPOmSpEsmpIVtWfnXkhhAoRsbZwo6z4oAuBJQs8EibsOr/r8KY5UHRbp+q3SlDhBE3mWszybMdOVRQKyJ1lZVXpmxmjXp/W2AqitsjCTKQaHi+
9 - 4xUnNjT8P0WiPtYg6ojrrQZnF0gU0wnndNQdLfPOMxoDvWjfe5OuEcY55yDRIosdpkeItTMVN1CRL4WecFgM8mBIVlnssE4Q1GM87PWNHipGZ91+MJwdsr0yUfCsJyRv