加密/解密:HMAC 标签在解密方法中不匹配
Posted
技术标签:
【中文标题】加密/解密:HMAC 标签在解密方法中不匹配【英文标题】:Encryption/decryption: HMAC tags don't match in decryption method 【发布时间】:2017-07-16 22:08:56 【问题描述】:我正在尝试编写加密/解密消息的加密和解密方法。
在 encrypt 方法中,它将接受一个字符串。它将读入公钥并用于 RSA 密码。然后,将使用具有 AES 密钥和 IV 的 AES 密码对消息进行加密。然后,将使用 HMAC 密钥生成带有密文加密的 AES 的 HMAC 标签。 AES 密钥和 HMAC 密钥连接在一起并由 RSA Cipher 加密。该方法将返回一个 JSONObject,其中包含:RSA 密文、AES 密文、AES IV 和 HMAC 标记。它们是转换为十六进制字符串的字节数组。
在解密方法中,它会接受 JSON 对象,该对象将被解析。它将读入将在 RSA 密码中使用的私钥。 RSA 密码将用于解密连接的密钥。解密后,密钥将分为 AES 密钥和 HMAC 密钥。然后,将在 AES 密文上生成新的 HMAC 标签。比较来自加密的标签和新标签。如果相等,则解密AES密文并获取消息。
当我运行我的代码时,没有错误,但它不会解密,因为 2 个标签不匹配。我不知道为什么。
公钥和私钥是从.pem
文件转换而来的.der
文件。
请帮助我。谢谢!
import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
import org.json.*;
import javax.xml.bind.DatatypeConverter;
public class CryptographicTools
/**
* This method encrypts a message
* @param message String message to be encrypted
* @return a JSONObject
*/
public JSONObject encryptMessage(String message)
JSONObject output = new JSONObject(); // instantiate JSONObject
try
//read in public key
byte[] publicKeyBytes = readKeyFromFile("public.der");//pem convert to der
//turn bytes into public key
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(publicKeyBytes); //encodes the bytes
KeyFactory keyFactory = KeyFactory.getInstance("RSA"); //make the key a RSA instance
//initialize RSA object and public key
Cipher RSAObject = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); //with OAEP
RSAObject.init(Cipher.ENCRYPT_MODE, keyFactory.generatePublic(publicSpec)); //create RSA encryption cipher with a generated public key
//generate 256-bit AES key
KeyGenerator keyGen = KeyGenerator.getInstance("AES");//generate AES Key
keyGen.init(256); //generate a key with 256 bits
SecretKey AESKey = keyGen.generateKey(); //generate AES key with 256 bits
//Create AES IV
SecureRandom randomByteGenerator = new SecureRandom();//secure generator to generate random byes for IV
byte[] AESKeyIVArray = new byte[16];
randomByteGenerator.nextBytes(AESKeyIVArray);//get random bytes for iv
IvParameterSpec AES_IV = new IvParameterSpec(AESKeyIVArray); //iv object for AES object
//initialize AES object
Cipher AESObject = Cipher.getInstance("AES/CBC/PKCS5Padding");
AESObject.init(Cipher.ENCRYPT_MODE, AESKey, AES_IV); //tell the AES object to encrypt
//encrypt message with AES
byte[] AESciphertext = AESObject.doFinal(message.getBytes());
//generate 256-bit HMAC key
byte[] SHA256KeyArray = new byte[32];//256 bits
randomByteGenerator.nextBytes(SHA256KeyArray);//generate random bits for key
SecretKeySpec HMACKeySpec = new SecretKeySpec (SHA256KeyArray,"HmacSHA256"); //make the key
Mac HMAC = Mac.getInstance("HmacSHA256"); //initialize HMAC
HMAC.init(HMACKeySpec);//put key in cipher
byte [] HMACTag = HMAC.doFinal(AESciphertext);//generate HMAC tag
//concatenate AES and HMAC keys
byte[] AESKeyByte = AESKey.getEncoded();///turn AESKey to byte array
byte[] HMACKeySpecByte = HMACKeySpec.getEncoded();///turn HMAXKey to byte array
byte[] concatenatedKeys = new byte[AESKeyByte.length + HMACKeySpecByte.length];//new array for concatenated keys
//combine keys in new array
System.arraycopy(AESKeyByte, 0, concatenatedKeys, 0, AESKeyByte.length);
System.arraycopy(HMACKeySpecByte, 0, concatenatedKeys, AESKeyByte.length, HMACKeySpecByte.length);
//encrypt keys with RSA object
byte[] RSAciphertext = RSAObject.doFinal(concatenatedKeys);
//put RSA ciphertext, AES ciphertext, AES_IV and HMAC tag in JSon
//save byte[] as Strings in hex
output.put("RSAciphertext", DatatypeConverter.printHexBinary(RSAciphertext));
output.put("AESciphertext", DatatypeConverter.printHexBinary(AESciphertext));
output.put("AES_IV", DatatypeConverter.printHexBinary(AES_IV.getIV()));
output.put("HMACTag", DatatypeConverter.printHexBinary(HMACTag));
catch (Exception e)
System.out.println("Error: " + e.toString() +e.getMessage()); //error message
return output; //return as JSON Object
/**
* This method decrypts a message
* @param jsonObjectEncrypted
* @return message as string
*/
public String decrypt (JSONObject jsonObjectEncrypted)
String message="";
try
//recover RSA ciphertext from JSON
String RSACiphertextString=jsonObjectEncrypted.getString("RSAciphertext");
byte[] recoveredRSAciphertext = DatatypeConverter.parseHexBinary(RSACiphertextString); //convert hex string to byte array
//recover AES ciphertext from JSON
String AESCiphertextString=jsonObjectEncrypted.getString("AESciphertext");
byte[] recoveredAESciphertext = DatatypeConverter.parseHexBinary(AESCiphertextString); //convert hex string to byte array
//recover AES IV from JSON
String AES_IVString=jsonObjectEncrypted.get("AES_IV").toString();
byte[] recoveredAES_IV = DatatypeConverter.parseHexBinary(AES_IVString); //convert hex string to byte array
//recover HMACTag from JSON
String HMACTagString=jsonObjectEncrypted.getString("HMACTag");
byte[] recoveredHMACTag = DatatypeConverter.parseHexBinary(HMACTagString); //convert hex string to byte array
//read in private key
byte[] privateKeyBytes = readKeyFromFile("private.der");//pem convert to der
//turn bytes into private key
PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(privateKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
//initialize RSA object and private key
Cipher RSAObject = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); //with OAEP
RSAObject.init(Cipher.DECRYPT_MODE, keyFactory.generatePrivate(privateSpec)); //create RSA encryption cipher with a generated private key
//Decrypt concatenated keys with RSA object
byte[] concatenatedKeys = RSAObject.doFinal(recoveredRSAciphertext);
//split the concatenated keys
byte[] AESKey = new byte[concatenatedKeys.length/2];
byte[] HMACKey = new byte[concatenatedKeys.length/2];
System.arraycopy(concatenatedKeys, 0,AESKey,0,AESKey.length); //Copy half into AESKey
System.arraycopy(concatenatedKeys, AESKey.length,HMACKey,0,HMACKey.length); //Copy Other half into HMACKey
//generate HMACTag
SecretKeySpec HMACKeySpec = new SecretKeySpec (HMACKey,"HmacSHA256"); //make the key
Mac HMAC = Mac.getInstance("HmacSHA256");
HMAC.init(HMACKeySpec);//initialize with HMAC Key
byte [] newHMACTag = HMAC.doFinal(recoveredAESciphertext); //generate HMACTag with AES Ciphertext
if(recoveredHMACTag.equals(newHMACTag)) //encrypt message if tags are equal
//initialize AES object
Cipher AESObject = Cipher.getInstance("AES/CBC/PKCS5Padding");
AESObject.init(Cipher.DECRYPT_MODE, new SecretKeySpec (AESKey,"AES"), new IvParameterSpec(recoveredAES_IV)); //tell the AES object to encrypt
message = new String (AESObject.doFinal(recoveredAESciphertext), "US-ASCII");//encrypt AES ciphertext and save as string
else
System.out.println("Message cannot be decrypted.");
catch (Exception e)
System.out.println("Error: "+e.toString()+": "+e.getMessage()); //error message
return message; //return plaintext
/**
* This method reads bytes of a key from a file into a byte array
* @param fileName type of key
* @return byte array
* @throws IOException
*/
public byte[] readKeyFromFile(String fileName) throws IOException
return Files.readAllBytes(Paths.get(fileName));
【问题讨论】:
【参考方案1】:Java 数组没有按照您希望的方式实现.equals()
(info)。尝试替换此检查:
recoveredHMACTag.equals(newHMACTag)
这个:
java.util.Arrays.equals(recoveredHMACTag, newHMACTag)
我不能说所有都可能导致它出错,但这是我要检查的第一件事。
【讨论】:
最好使用常数时间比较函数如:***.com/a/30981412/1816580 在比较哈希值时,很少需要恒定时间比较,而这似乎不是这种情况。我很想在这里看到这种攻击的概念。 @zaph 如果攻击者知道(或可以猜到)消息的内容,他们可以进行位翻转攻击来更改它(使用 CBC 在进程中混淆块)。然后他们使用这个新消息进行定时攻击以获得有效的 HMAC。最终结果是他们得到了一条被接受为有效的伪造消息。实现起来很棘手,可能并不适用于所有情况,但这就是攻击。 (在这种情况下,情况更糟——MAC 没有覆盖 IV,因此攻击者可以随意更改第一个块的内容)。 我不明白定时攻击是如何应用于这个哈希的,正确的一位似乎无助于正确的其他位,因为输入中的一位变化大约会改变 50%哈希输出位。我并没有说没有任何情况下对哈希的定时攻击不起作用,只是不是这个。如前所述,在 HMAC 中不包括 IV 是错误的。 @zaph 在这种情况下,哈希的输入不会改变。攻击者有一个修改过的消息,为了让它被接受,需要有一个有效的身份验证标签。他们反复修改标签,试图让它与计算的值相匹配,(这是恒定的,因为消息和密钥不会改变)。以上是关于加密/解密:HMAC 标签在解密方法中不匹配的主要内容,如果未能解决你的问题,请参考以下文章