我在Java中使用模式“ AES / CBC / pkcs7padding”在2个设备之间进行通信。
建立通信后,两个设备之一将分配一个随机IV并将其发送给另一台设备。接收者将使用此IV实例化一个cryptoCipher和一个解密Cipher(请参见下面的代码)。
注意:在这里,我只输入用于加密的代码,但是我们具有用于解密的类似代码。
IV矢量已在通信开始时发送。然后,我们希望2个设备交换加密的消息而不再发送IV。 如果我们的理解是正确的,只要没有消息丢失,两个设备都应该知道在XOR中使用的当前“向量”是什么。
但是,Java代码无法正常运行: 如果我连续两次调用crypto(),它将产生相同的加密数据。 这不是我们期望的,因为我们认为第一次加密的结果将是第二次加密的“向量”(此向量与https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CBC上所示的plainText之间的XOR)。
我们的理解错误吗?我们在实现过程中错过了什么吗?
private Cipher mEncryptionCipher;
private void createEncryptionCipher(byte[] iv) {
mEncryptionCipher = null;
try {
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
mEncryptionCipher = Cipher.getInstance("AES/CBC/pkcs7padding", "BC");
mEncryptionCipher.init(Cipher.ENCRYPT_MODE, mAESKey, ivParameterSpec);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
}
private byte[] encrypt(byte[] data) {
if (mEncryptionCipher == null) {
Log.e(TAG, "Invalid mEncryptionCipher!");
return null;
}
try {
int sizeOfEncryptedData = computeLengthAfterPKCS7Padding(data.length);
byte[] encodedData = new byte[sizeOfEncryptedData];
int cipherBytes = mEncryptionCipher.update(data, 0, data.length, encodedData, 0);
//allways call doFinal
cipherBytes += mEncryptionCipher.doFinal(encodedData, cipherBytes);
return encodedData;
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (ShortBufferException e) {
e.printStackTrace();
} catch (STException e) {
e.printStackTrace();
}
return null;
}
答案 0 :(得分:2)
正如其他人所建议的那样,请尽可能使用TLS。
否则,至少要从TLS的设计中汲取教训(错误)。在TLS 1.0(https://tools.ietf.org/html/rfc2246)中,CBC密码状态是在单独的记录中维护的,我认为这是您正在尝试对“数据包”进行的操作。在TLS 1.1(https://tools.ietf.org/html/rfc5246)中对此进行了更改,以便每个记录都包含一个明确的IV。每条记录的IV“应该随机选择,并且必须不可预测”。因此,每条记录都是独立加密的。
请特别注意RFC 5246中的安全分析这一部分:
F.3。显式静脉注射
[CBCATT]描述了针对TLS的选定明文攻击,具体取决于 知道IV的记录。使用旧版TLS [TLS1.0]
先前记录的CBC残基作为IV,因此
启用了此攻击。此版本使用显式IV,以
防范这种攻击。
还有其他陷阱在等待您,所以我回到我的第一个建议:尽可能使用TLS。
编辑:糟糕,TLS 1.1 RFC实际上是https://tools.ietf.org/html/rfc4346,它具有相同的F.3部分。
答案 1 :(得分:0)
如蜘蛛侠鲍里斯(Boris)和詹姆斯·K·波克(James K Polk)所指出的,doFinal()重置密码对象的状态,因此,如果我们进行新的加密,它将从原始IV重新开始,这不是CBC想要的行为。
我已经尝试过以下解决方案,并且可以使用。我正在检索上一个加密的块,并将其用作下一次加密的IV。
private byte[] mEncryptionIV = new byte[INITIALIZATION_VECTOR_SIZE];
private byte[] encrypt(byte[] data) {
try {
int sizeOfEncryptedData = computeLengthAfterPKCS7Padding(data.length);
byte[] encodedData = new byte[sizeOfEncryptedData];
IvParameterSpec ivParameterSpec = new IvParameterSpec(mEncryptionIV);
Cipher encryptionCipher = Cipher.getInstance("AES/CBC/pkcs7padding", "BC");
encryptionCipher.init(Cipher.ENCRYPT_MODE, mAESKey, ivParameterSpec);
int cipherBytes = encryptionCipher.update(data, 0, data.length, encodedData, 0);
//always call doFinal
cipherBytes += encryptionCipher.doFinal(encodedData, cipherBytes);
// The last encrypted block will be used as IV for the next encryption
System.arraycopy(encodedData, encodedData.length-AES_BLOCK_LENGTH, mEncryptionIV, 0, mEncryptionIV.length);
return encodedData;
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (ShortBufferException e) {
e.printStackTrace();
} catch (STException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
}
return null;
}