问题
I'm using Pycryptodome (a PyCrypto fork) to create AES-GCM ciphertexts. I use the following Python code to encrypt:
cek = os.urandom(16)
nonce = os.urandom(12)
cipher = AES.new(cek, AES.MODE_GCM, nonce=nonce, mac_len=16)
ciphertext = cipher.encrypt(message)
I then pass this to Java to decrypt:
byte[] nonce = new byte[12];
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv);
SecretKeySpec secretKeySpec = new SecretKeySpec(cek, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(nonce);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmSpec);
byte[] decBytes = mCipher.doFinal(cipherText);
However, I get the following error:
Exception in thread "main" javax.crypto.AEADBadTagException: Tag mismatch!
at com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:524)
at com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1023)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:960)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:824)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:436)
at javax.crypto.Cipher.doFinal(Cipher.java:2165)
回答1:
You're missing one thing: Pycryptodome does not add the hash tag to the message - you have to append it to the encrypted message:
E.g.
ciphertext, tag = cipher.encrypt_and_digest(message)
ciphertext = ciphertext + tag
回答2:
Thanks to Alastair McCormack's answer above, here is what worked for me (Python code):
from Crypto.PublicKey import RSA
from Crypto.Random import get_random_bytes
from Crypto.Cipher import AES, PKCS1_OAEP
from Crypto.Hash import SHA256, SHA1
from Crypto.Signature import pss
from base64 import b64encode
data = 'hello world'.encode("utf-8")
with open("joe.pub", "rb") as f:
encodedKey = f.read()
pubkey = RSA.importKey(encodedKey)
if pubkey.has_private():
raise Exception('need a public key for encryption')
session_key = get_random_bytes(16)
# Encrypt the session key with the public RSA key
cipher_rsa = PKCS1_OAEP.new(pubkey, hashAlgo=SHA256, mgfunc=lambda x,y: pss.MGF1(x,y, SHA1))
enc_session_key = cipher_rsa.encrypt(session_key)
# Encrypt the data with the AES session key
cipher_aes = AES.new(session_key, AES.MODE_GCM)
ciphertext, tag = cipher_aes.encrypt_and_digest(data)
ciphertext = ciphertext + tag
mesg = ''.join([x for x in (enc_session_key, cipher_aes.nonce, tag, ciphertext)])
print b64encode(mesg)
And the associated Java code:
import java.io.FileReader;
import java.io.BufferedReader;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.Security;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.GCMParameterSpec;
import java.util.Base64;
import java.util.Arrays;
public class So
{
static {
try {
@SuppressWarnings("unchecked")
Class<Provider> c = (Class<Provider>)Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
Security.addProvider(c.getDeclaredConstructor().newInstance());
} catch(java.lang.ClassNotFoundException |
java.lang.NoSuchMethodException |
java.lang.InstantiationException |
java.lang.IllegalAccessException |
java.lang.reflect.InvocationTargetException ex) {
System.err.println("BouncyCastle not found");
}
}
static private byte[] loadPvtKey(String filePath) throws java.io.IOException
{
BufferedReader in = null;
try {
in = new BufferedReader(new FileReader(filePath));
/* read, check and discard first line. */
String line = in.readLine();
if ( ! line.equals("-----BEGIN PRIVATE KEY-----") )
throw new IllegalArgumentException(filePath + ": not a private key file");
StringBuilder sbuf = new StringBuilder();
while ((line = in.readLine()) != null) {
if ( line.equals("-----END PRIVATE KEY-----") ) break;
sbuf.append(line);
}
return Base64.getDecoder().decode(sbuf.toString());
} finally {
try { if ( in != null ) in.close(); }
catch(java.io.IOException ex) {}
}
}
static public void main(String[] args) throws Exception
{
if ( args.length != 2 ) {
System.err.println("usage: java Decrypt pvtKeyFile encString64");
System.exit(1);
}
int index = 0;
String pvtKeyFile = args[index++];
String encString64 = args[index++];
byte[] encBytes = Base64.getDecoder().decode(encString64);
System.err.println("encrypted bytes: " + encBytes.length);
byte[] bytes = loadPvtKey(pvtKeyFile);
PKCS8EncodedKeySpec ks = new PKCS8EncodedKeySpec(bytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey pvt = kf.generatePrivate(ks);
Base64.Encoder encoder = Base64.getEncoder();
byte[] encSessionKey = Arrays.copyOfRange(encBytes, 0, 256);
System.err.printf("encSessionKey -> %s\n", encoder.encodeToString(encSessionKey));
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
rsaCipher.init(Cipher.DECRYPT_MODE, pvt);
byte[] sessionKey = rsaCipher.doFinal(encSessionKey);
System.err.printf("sessionKey -> %s\n", encoder.encodeToString(sessionKey));
byte[] iv = Arrays.copyOfRange(encBytes, 256, 256+16);
System.err.printf("iv -> %s\n", encoder.encodeToString(iv));
GCMParameterSpec ivSpec = new GCMParameterSpec(128, iv);
Cipher aesCipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec aesKey = new SecretKeySpec(sessionKey, "AES");
aesCipher.init(Cipher.DECRYPT_MODE, aesKey, ivSpec);
byte[] tag = Arrays.copyOfRange(encBytes, 256+16, 256+32);
System.err.printf("tag[%d] -> %s\n", tag.length, encoder.encodeToString(tag));
byte[] cipherText = Arrays.copyOfRange(encBytes, 256+32, encBytes.length);
System.err.printf("cipherText -> %s\n", encoder.encodeToString(cipherText));
byte[] clearText = aesCipher.doFinal(cipherText);
System.err.printf("clearText -> %s\n", new String(clearText, "UTF-8"));
}
}
来源:https://stackoverflow.com/questions/50396221/pycrypto-aes-gcm-encryption-and-java-decryption