There is Alice and Bob. I want to realize the following process:
Alice encrypts a text with AES and generates a secret key
Alice encrypts this secret key with Bobs public key using Elliptic Curves with El Gamal
Alice sends the encrypted text & encrypted secret key to Bob
Bob decrypts the secret key with his private key
Bob decrypts the text with the decrypted secret key
Done
I am using the class ECElGamalEncryptor from bouncycastle. My problem is, that as far as I understand, this class encrypts a point on an Elliptic Curve using a public key but my AES secret key is not a ECPoint but a Hexadecimal.
Lets pretend I have this 128-Bit Secret Key for the AES encryption:
6D5A7134743777397A24432646294A40
And this is what I have so far:
import java.math.BigInteger;
import java.security.SecureRandom;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.asn1.sec.SECNamedCurves;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.ec.ECElGamalDecryptor;
import org.bouncycastle.crypto.ec.ECElGamalEncryptor;
import org.bouncycastle.crypto.ec.ECPair;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.math.ec.ECPoint;
class TestClass {
public static void main(String[] argv) {
// Get domain parameters for example curve secp256r1
X9ECParameters ecp = SECNamedCurves.getByName("secp256r1");
ECDomainParameters domainParams = new ECDomainParameters(ecp.getCurve(),
ecp.getG(), ecp.getN(), ecp.getH(),
ecp.getSeed());
// Generate a private key and a public key
AsymmetricCipherKeyPair keyPair;
ECKeyGenerationParameters keyGenParams = new ECKeyGenerationParameters(domainParams, new SecureRandom());
ECKeyPairGenerator generator = new ECKeyPairGenerator();
generator.init(keyGenParams);
keyPair = generator.generateKeyPair();
ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters) keyPair.getPrivate();
ECPublicKeyParameters publicKey = (ECPublicKeyParameters) keyPair.getPublic();
byte[] privateKeyBytes = privateKey.getD().toByteArray();
// Get ECPoint Q from privateKey
ECPoint Q = domainParams.getG().multiply(new BigInteger(privateKeyBytes));
//Initialize ECElGamalEncryptor
ECElGamalEncryptor elgamalEn = new ECElGamalEncryptor();
elgamalEn.init(publicKey);
ECPair encrypted = elgamalEn.encrypt(Q);
//Encryption
ECElGamalDecryptor elgamalDe = new ECElGamalDecryptor();
elgamalDe.init(privateKey);
ECPoint original = elgamalDe.decrypt(encrypted);
}
}
So I am able to initialize the ECElGamalEncryptor and to encrypt ECPoint Q with the public Key. But actually, I want to encrypt the AES Secret key and I have no idea what I have to do now.
Let me try rephrasing a part of your question to make it much more clear along with some symbols necessary. The way you worded your scheme is slightly tedious to understand. However, as #JamesKPolk and #MaartenBodewes pointed out what you'd need for Elliptic Curve cryptography which supports encryption is an IES scheme called ECIES which can be obtained as a combination of the ECDH and a Symmetric encryption scheme like AES for example. So let's revisit the scheme you've been trying to implement with Alice and Bob.
Bootstrap
Alice and Bob generate their respective AES Keys which consist of a SecretKey and an IV. In this example we'll use AES256
Alice and Bob generate their corresponding EC KeyPairs and in some way share their PublicKeys so that each one has the knowledge of the other public key.
Alice has Bob's public key.
Bob has Alice's public key.
Scheme Desired
Alice encrypts a plain text message m with AES to generate encrypted message em.
Before this can happen Alice generates an AES Key which contains the SecretKey used for encryption and an IV vector. In the code example that'll follow we'll call this tuple as an AESPair.
Alice encrypts the SecretKey (SK) || IV message with Bobs public key using ECIES to obtain esk||iv.
Alice generates the SharedSecret using Alice's Private Key and Bob's Public Key. Let's call this SSK1
Bob can generate the SharedSecet using Bob's Private Key and Alice's Public Key. Let's call this SSK2
At this point SSK1==SSK2. You can find the reason for this here in the Decryption section of IES.
Alice sends the encrypted text em & encrypted secret key & IV parameters necessary (esk||iv) to Bob.
Bob decrypts the encrypted message containing the AES secret and IV (esk||iv) with his private key to obtain (SK || IV)
Bob decrypts the encrypted text em with the obtained secret key in Step 4 to receive the original message Alice sent i.e. m
Done
Code
Helper Functions
private final static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String convertBytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for ( int j = 0; j < bytes.length; j++ ) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars).toLowerCase();
}
public static byte[] hexStringToByteArray(String hexString){
byte[] bytes = new byte[hexString.length() / 2];
for(int i = 0; i < hexString.length(); i += 2){
String sub = hexString.substring(i, i + 2);
Integer intVal = Integer.parseInt(sub, 16);
bytes[i / 2] = intVal.byteValue();
String hex = "".format("0x%x", bytes[i / 2]);
}
return bytes;
}
ECC.java
public class ECC {
// Both Alice and Bob agree upon this value in some manner before starting this protocol.
public static byte[] iv = new SecureRandom().generateSeed(16);
static {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
}
public static KeyPair generateKeyPair() throws InvalidAlgorithmParameterException, NoSuchProviderException, NoSuchAlgorithmException {
ECNamedCurveParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec("secp256r1");
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ECDH", "BC");
keyPairGenerator.initialize(parameterSpec);
return keyPairGenerator.generateKeyPair();
}
public static SecretKey generateSharedSecret(PrivateKey privateKey, PublicKey publicKey) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException {
KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH", "BC");
keyAgreement.init(privateKey);
keyAgreement.doPhase(publicKey, true);
return keyAgreement.generateSecret("AES");
}
public static byte[] encrypt(SecretKey key, byte[] plainTextBytes) throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, InvalidKeyException, ShortBufferException, BadPaddingException, IllegalBlockSizeException {
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
byte[] cipherText;
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
cipherText = new byte[cipher.getOutputSize(plainTextBytes.length)];
int encryptLength = cipher.update(plainTextBytes, 0, plainTextBytes.length, cipherText, 0);
encryptLength += cipher.doFinal(cipherText, encryptLength);
return cipherText;
}
public static byte[] decrypt(SecretKey key, byte[] cipherTextBytes) throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, InvalidKeyException, ShortBufferException, BadPaddingException, IllegalBlockSizeException {
Key decryptionKey = new SecretKeySpec(key.getEncoded(),
key.getAlgorithm());
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
byte[] plainText;
cipher.init(Cipher.DECRYPT_MODE, decryptionKey, ivSpec);
plainText = new byte[cipher.getOutputSize(cipherTextBytes.length)];
int decryptLength = cipher.update(cipherTextBytes, 0, cipherTextBytes.length, plainText, 0);
decryptLength += cipher.doFinal(plainText, decryptLength);
return plainText;
}
}
AES256.java
public class AES256 {
public static AESPair generateKeyPair() throws NoSuchAlgorithmException {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(256);
SecretKey key = keyGenerator.generateKey();
byte[] IV = new byte[16];
SecureRandom random = new SecureRandom();
random.nextBytes(IV);
AESPair response = new AESPair(key, IV);
return response;
}
public static byte[] encrypt(byte[] plainText, SecretKey key, byte[] IV) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(IV);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec);
byte[] cipherText = cipher.doFinal(plainText);
return cipherText;
}
public static byte[] decrypt(byte[] cipherText, SecretKey key, byte[] IV) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");
IvParameterSpec ivSpec = new IvParameterSpec(IV);
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] decryptedText = cipher.doFinal(cipherText);
return decryptedText;
}
public static byte[] serializeSecretKey (SecretKey key) {
return key.getEncoded();
}
public static SecretKey deserializeSecretKey (byte[] sk) {
return new SecretKeySpec(sk, 0, sk.length, "AES");
}
}
AESPair.java which is the corresponding helper for AES.
public class AESPair {
private SecretKey key;
private byte[] IV;
public void setIV(byte[] IV) {
this.IV = IV;
}
public void setKey(SecretKey key) {
this.key = key;
}
public byte[] getIV() {
return IV;
}
public SecretKey getKey() {
return key;
}
public AESPair(SecretKey sk, byte[] ivBytes) {
key = sk;
IV = ivBytes;
}
// This takes in SK || IV for AES256 and creates the SecretKey object and corresponding IV byte array.
public AESPair(byte[] skConcatIVBytes) {
int total_bytes = skConcatIVBytes.length;
// FOR AES256 the key is 32 bytes and the IV is 16 bytes
byte[] sk = Arrays.copyOfRange(skConcatIVBytes, 0, 32);
byte[] iv = Arrays.copyOfRange(skConcatIVBytes, 32, total_bytes);
key = new SecretKeySpec(sk, 0, sk.length, Constant.AES);
IV = iv;
}
}
Now that we have the pieces we need, let's put together the scheme desired as a test.
#Test
public void test_scheme_ecc() throws NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchPaddingException, NoSuchProviderException, ShortBufferException {
String plainText = "plaintext message from alice to bob";
System.out.println("Original plaintext message: " + plainText);
AESPair aliceAESPair = AES256.generateKeyPair();
AESPair bobAESPair = AES256.generateKeyPair();
byte[] encryptedPlainTextMessageFromAlice = AES256.encrypt(plainText.getBytes(StandardCharsets.UTF_8), aliceAESPair.getKey(), aliceAESPair.getIV());
System.out.println("Alice encrypted message : " + convertBytesToHex(encryptedPlainTextMessageFromAlice));
// Necessary Key + IV information to reconstruct the key
byte[] keyInformation = ByteBuffer.allocate(aliceAESPair.getKey().getEncoded().length + aliceAESPair.getIV().length)
.put(aliceAESPair.getKey().getEncoded())
.put(aliceAESPair.getIV())
.array();
System.out.println("Alice's SK || IV : " + convertBytesToHex(keyInformation));
// Initialize two key pairs
KeyPair aliceECKeyPair = ECC.generateKeyPair();
KeyPair bobECKeyPair = ECC.generateKeyPair();
System.out.println("Alice EC PK : " + convertBytesToHex(aliceECKeyPair.getPublic().getEncoded()));
System.out.println("Bob EC PK : " + convertBytesToHex(bobECKeyPair.getPublic().getEncoded()));
// Create two AES secret keys to encrypt/decrypt the message
SecretKey aliceSharedSecret = ECC.generateSharedSecret(aliceECKeyPair.getPrivate(), bobECKeyPair.getPublic());
System.out.println("Alice Shared Secret Key : " + convertBytesToHex(aliceSharedSecret.getEncoded()));
// Encrypt the message using 'aliceSharedSecret'
byte[] cipherText = ECC.encrypt(aliceSharedSecret, keyInformation);
System.out.println("Encrypted cipher text: " + convertBytesToHex(cipherText));
// Decrypt the message using 'bobSharedSecret'
SecretKey bobSharedSecret = ECC.generateSharedSecret(bobECKeyPair.getPrivate(), aliceECKeyPair.getPublic());
System.out.println("Bob Shared Secret Key : " + convertBytesToHex(bobSharedSecret.getEncoded()));
byte[] decrypted_EncryptedTextFromAlice = ECC.decrypt(bobSharedSecret, cipherText);
System.out.println("Decrypted cipher text to obtain Alice generated secret key: " + convertBytesToHex(decrypted_EncryptedTextFromAlice));
AESPair reconstructedKey = new AESPair(decrypted_EncryptedTextFromAlice);
byte[] decryptedText = AES256.decrypt(encryptedPlainTextMessageFromAlice, reconstructedKey.getKey(), reconstructedKey.getIV());
System.out.println("Decrypted plain text message : " + new String(decryptedText));
}
Here's a run from the test:
Original plaintext message: plaintext message from alice to bob
Alice encrypted message : 9d273ea89ab6b8d170941d2578f0d4e11b1d6a3be199189dbbf4a5ff64fbf1348edbb459e38dac17aad6a68b1a95300f
Alice's SK || IV : 857248ab0171a652926fcc46353831965dd2d98cb4920de7d629c07250bc60fb60306f67d2c44e725b2e8344d970b34b
Alice EC PK : 3059301306072a8648ce3d020106082a8648ce3d030107034200042499c59fea8ab010782444825c7872c04407a4f034d907ca9014b9f8d4be1226cb9fc9eff57f8e0e7b8e1aa83290c6d6c3a56aeeef3490e1e55476e94abb4128
Bob EC PK : 3059301306072a8648ce3d020106082a8648ce3d03010703420004d91562882f30b54177449941b9812b17ac5a59d2b80cc5fbaef833426152623dfb17965ba9897edd5da26b4044071882f8ae53ce37c24f0ea5b55b7e42b689ac
Alice Shared Secret Key : 3fa7b4ae68ff51296293b69ac1b0d8d139bf3f6a60732a124734a19f2987b772
Encrypted cipher text: 758506913bee96816f7a3190720ce7f01ddb8acbeaef1e669af420c04036a4b2ab446ce2a2bee62f603a0400b9076c927f2eeffc2a4cec0ffad756fed19dc6d9
Bob Shared Secret Key : 3fa7b4ae68ff51296293b69ac1b0d8d139bf3f6a60732a124734a19f2987b772
Decrypted cipher text to obtain Alice generated secret key: 857248ab0171a652926fcc46353831965dd2d98cb4920de7d629c07250bc60fb60306f67d2c44e725b2e8344d970b34b
Decrypted plain text message : plaintext message from alice to bob
BUILD SUCCESSFUL in 1s
Explanation of the test case code
An AES256 key and IV is generated for Alice
A plain text message "plain text from alice to bob" is encrypted by Alice using the key generated in step 1.
A new byte array is created with the concatenation of key || IV of Alice's key. This is the message that should be encrypted and sent to Bob via ECIES.
Alice and Bob generate their Elliptic Curve KeyPairs and we assume they known each others' public keys. The key generation happens in the ECC.generateKeyPair() method.
Alice uses Bob's public key and Alice's private key to generate a Shared Secret which is a symmetric SecretKey
Alice encrypts the message in step 3 using the shared secret key in step 5 which creates the encrypted message that needs to be sent to Bob.
Bob receives the messages (Step 6 and Step 2) and computes the shared secret key using Bob's private key and Alice's public key.
Bob uses the key constructed in step 7 to decrypt the encrypted message that was received from Alice.
Now Bob knows the AES Key that Alice used in Step 2 to encrypt the original plain text message. The message obtained after decryption is a byte[] which is converted to an AESPair object creating the SecretKey and IV necessary.
Bob decrypts the encrypted message in Step 2 and recovers the original message "plain text from alice to bob"
Hope this helps. Do let me know if you wanted some clarifications.
Related
As per given steps, I have performed encryption but am getting an error.
Generate a 16-digit random number (session key). Say RANDOMNO.
RANDOMNO = 1111222233334444
Encrypt RANDOMNO using RSA/ECB/PKCS1Padding and encode using Base64. Say ENCR_KEY.
ENCR_KEY = B64Encode(RSA/ECB/PKCS1Encryption(RANDOMNO,ICICIPubKey.cer))
Perform AES/CBC/PKCS5Padding encryption on request payload using RANDOMNO as key and ivinitialization vector. Say ENCR_DATA.
ENCR_DATA = B64Encode(AES/CBC/PKCS5Padding(REQUEST_DATA, RANDOMNO, IV))
Now the client may choose to send IV in request from one of the two options below.
Send Base64 Encoded IV in “iv” tag.
public byte[] generateRandomBytes() {
SecureRandom ng=new SecureRandom();
byte[] randomBytes=new byte[16];
ng.nextBytes(randomBytes);
return randomBytes;
}
//new method for encryption -we need to check
public String encryptRandomKeyWithCertificate(byte[] randomNumber) throws BadPaddingException, IllegalBlockSizeException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, FileNotFoundException, CertificateException {
//step2: encrypt the random number with certificate
FileInputStream fin = new FileInputStream("D:\\cedge_uat\\ICICIUATpubliccert.cer");
CertificateFactory f = CertificateFactory.getInstance("X.509");
X509Certificate certificate = (X509Certificate) f.generateCertificate(fin);
PublicKey publicKey = certificate.getPublicKey();
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] cipherData = cipher.doFinal(randomNumber);
String encodedData = Base64.getEncoder().encodeToString(cipherData);
return encodedData;
}
public String encryptRequestWithKey(String text, byte[] randomNumber) throws Exception {
//step3: encrypt the requestString with randomkeyEncrypted
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
BASE64Decoder decoder = new BASE64Decoder();
SecretKeySpec keySpec = new SecretKeySpec(randomNumber, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(new byte[16]);
cipher.init(Cipher.ENCRYPT_MODE, keySpec,ivSpec);
byte[] cipherData = cipher.doFinal(text.getBytes());
// BASE64Encoder encoder = new BASE64Encoder();
//return encoder.encode(cipherData).replaceAll("[\r\n]+", "");
String encodedData = Base64.getEncoder().encodeToString(cipherData);
return encodedData;
}
public static void main(String[] args) throws IOException {
String requestString = "CORP_USER=";
byte[] randomNumber;
String encryptedKey;
try {
randomNumber=encryption.generateRandomBytes();
encryptedKey = encryption.encryptRandomKeyWithCertificate(randomNumber);
String encryptedData =encryption.encryptRequestWithKey(requestString,randomNumber);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
According to the Wikipedia page on AES encryption, an AES key can be 128, 192 or 256 bits; i.e. 16, 24 or 32 bytes.
You are supplying a key whose size depends on the "random" number string you are generating. It looks like it will be between 1 and 19 digits plus a possible sign. When you call getBytes() on that string, you will get a byte array with anywhere between 1 and 20 bytes. That is typically NOT one of the acceptable key sizes for AES.
What you should do is use SecureRandom.nextBytes(byte[]) and supply a byte array of one of the three acceptable key sizes for AES.
First, please excuse my bad english, this is not my native language.
i am trying to encode and then decode some text of different lengths.
I tried to encode some text with RSA algorithm in java. In the progress, I found out that RSA-encoding is limited to a specific size of bytes, so this approach doesn't fit my specs as my text could be large.
As I want to stay with a public and a private key only, I came up with the idea to use a 'hard-coded' AES key which will be encoded with RSA public key and than use this to encode the data. (see code)
//harcoded secretkey base64 encoded
private static final String secretkey_base64 = "xtnN6Pove5AovbLXtGRJKw==";
//this is how RSA key are generated
public static KeyPair genKeys() throws Exception {
KeyPairGenerator kpg = KeyPairGenerator.getInstance( "RSA" );
kpg.initialize(2048);
KeyPair kp = kpg.generateKeyPair();
return kp;
}
//here text should be encoded
public static String encodeText(String plainText, PublicKey publicKey) throws Exception
{
Cipher encryptCipher = Cipher.getInstance("RSA");
encryptCipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] cipherText = encryptCipher.doFinal(CryptoUtil.secretkey_base64.getBytes(UTF_8));
String encodedSecKey = Base64.getEncoder().encodeToString(cipherText);
SecretKeySpec k = new SecretKeySpec(Base64.getDecoder().decode(encodedSecKey), "AES");
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.ENCRYPT_MODE, k);
byte[] byteCipherText = aesCipher.doFinal(plainText.getBytes());
return Base64.getEncoder().encodeToString(byteCipherText);
}
Obviously this doesn't work because the RSA encoded AES key is too long for AES encryption. Throws "Invalid AES key length: 256 bytes"
Does anyone have an idea how I could get the encryption running with this particular approach ?
Is this even possible, or do I have to use a different approach?
Thanks in advance.
//edit - Solved thanks to #ewramner
// here is the working solution:
public static String encodeText(String plainText, PublicKey publicKey) throws Exception
{
KeyGenerator generator = KeyGenerator.getInstance("AES");
generator.init(128); // The AES key size in number of bits
SecretKey secKey = generator.generateKey();
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.PUBLIC_KEY, publicKey);
byte[] encSecKey = cipher.doFinal(secKey.getEncoded());
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.ENCRYPT_MODE, secKey);
byte[] byteCipherText = aesCipher.doFinal(plainText.getBytes());
return Base64.getEncoder().encodeToString(encSecKey) + "#" + Base64.getEncoder().encodeToString(byteCipherText);
}
public static String decodeText(String cipherText, PrivateKey privateKey) throws Exception
{
String[] split = cipherText.split("#");
String encSecKey = split[0];
String encText = split[1];
byte[] bytesSecKey = Base64.getDecoder().decode(encSecKey);
byte[] bytesText = Base64.getDecoder().decode(encText);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.PRIVATE_KEY, privateKey);
byte[] decSecKey = cipher.doFinal(bytesSecKey);
SecretKey secKey = new SecretKeySpec(decSecKey , 0, decSecKey .length, "AES");
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.DECRYPT_MODE, secKey);
return new String(aesCipher.doFinal(bytesText));
}
I wrote a simple Encryption and Decryption helper class for my android app to encrypt and store Strings securely.
It consists of a single static public method to encrypt, then it calls a private static method to decrypt the encrypted message and returns it. I wrote the method this way to check if the message is intact after encryption/decryption.
I wrote a simple JUnit test with a String and called AssertEquals on the String before and after sending it to the Crypto encryption method.
I get this following errors from running the test:
javax.crypto.AEADBadTagException: Tag mismatch!
The error stack:
at com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:571)
at com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1046)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:983)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:845)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
at javax.crypto.Cipher.doFinal(Cipher.java:2165)
at util.Crypto.decrypt(Crypto.java:94)
at util.Crypto.encrypt(Crypto.java:64)
at com.example.ali.meappley.CryptoTest.encryptAndDecryptTest(CryptoTest.java:29)
I'm new to cryptography, but I read different stackoverflow replies and couldn't find anything of help. Some users suggested calling cipher.update(someByteArray) before calling cipher.doFinal(someByteArray) but I couldnt manage to get it working. Any suggestions?
This is my helper class
public class Crypto {
//public methods
//public static encrypt method
public static String encrypt(String messageToEncrypt, #Nullable byte[] associatedData) throws NoSuchPaddingException,
NoSuchAlgorithmException,
InvalidAlgorithmParameterException,
InvalidKeyException,
BadPaddingException,
IllegalBlockSizeException {
byte[] plainBytes = messageToEncrypt.getBytes();
/////////////////////////////////////////////////////////////////
SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[16];
secureRandom.nextBytes(key);
SecretKey secretKey = new SecretKeySpec(key, "AES");
byte[] iv = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY
secureRandom.nextBytes(iv);
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); //128 bit auth tag length
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
if (associatedData != null) {
cipher.updateAAD(associatedData);
}
byte[] cipherText = cipher.doFinal(plainBytes);
ByteBuffer byteBuffer = ByteBuffer.allocate(4 + iv.length + cipherText.length);
byteBuffer.putInt(iv.length);
byteBuffer.put(iv);
byteBuffer.put(cipherText);
byte[] cipherMessage = byteBuffer.array();
Arrays.fill(key,(byte) 0); //overwrite the content of key with zeros
///////////////////////////////////////////////////////////////////
byte[] decrypted = decrypt(cipherMessage, null, key);
return decrypted.toString();
}
//public static decrypt method
private static byte[] decrypt(byte[] cipherMessage, #Nullable byte[] associatedData, byte[] key) throws NoSuchPaddingException,
NoSuchAlgorithmException,
InvalidAlgorithmParameterException,
InvalidKeyException,
BadPaddingException,
IllegalBlockSizeException {
ByteBuffer byteBuffer = ByteBuffer.wrap(cipherMessage);
int ivLength = byteBuffer.getInt();
if(ivLength < 12 || ivLength >= 16) { // check input parameter
throw new IllegalArgumentException("invalid iv length");
}
byte[] iv = new byte[ivLength];
byteBuffer.get(iv);
byte[] cipherText = new byte[byteBuffer.remaining()];
byteBuffer.get(cipherText);
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(128, iv));
if (associatedData != null) {
cipher.updateAAD(associatedData);
}
cipher.update(cipherText);
byte[] plainText= cipher.doFinal(cipherText);
return plainText;
}
There are a few issues with your code:
1) In your encrypt-method remove the following line (or shift it behind the decrypt-call).
Arrays.fill(key, (byte) 0); // overwrite the content of key with zeros
Otherwise the key for encryption and decryption differ.
2) In your encrypt-method also pass the associatedData in your decrypt-call i.e. replace
byte[] decrypted = decrypt(cipherMessage, null, key);
with
byte[] decrypted = decrypt(cipherMessage, associatedData, key);
The associatedData passed for encryption and decryption have to match for validity. For the purpose of the associatedData see e.g. https://crypto.stackexchange.com/questions/6711/how-to-use-gcm-mode-and-associated-data-properly
3) In your decrypt-method remove the line
cipher.update(cipherText);
For the purpose of the update-method see e.g. What does cipher.update do in java?
All three issues give rise to an AEADBadTagException.
4) I suspect for testing purposes your encrypt-method returns decrypted.toString() which however only gives you the object's class and hashcode. It would make more sense to return e.g. new String(decrypted).
I am using AESCrypt (gradle :compile 'com.scottyab:aescrypt:0.0.1')
to encrypt and decrypt the data.
TextView tv=(TextView)findViewById(R.id.demotext);
String encrypted="",decrypted="";
try {
encrypted = AESCrypt.encrypt("password","This is the best thing to go by");
decrypted = AESCrypt.decrypt("password",encrypted);
} catch (GeneralSecurityException e) {
e.printStackTrace();
}
System.out.println("EncryptedData:"+encrypted);
System.out.println("DecryptedData:"+decrypted);
tv.setText("Encrypted:"+encrypted +"\n"+"Decrypted:"+decrypted);
The code works perfectly fine in this case, I get the same input as decrypted text.
But, when I try to use already encrypted string using the same method (AES) from the site http://aesencryption.net/ as shown in the screenshot:
And copy paste that encrypted text like:
decrypted = AESCrypt.decrypt("password","sttA+FbNm3RkTovjHI8CcAdStXiMl45s29Jqle+y+pA=");
And then run the code then I get error saying :
javax.crypto.BadPaddingException: error:1e06b065:Cipher functions:EVP_DecryptFinal_ex:BAD_DECRYPT
But when I use the decrypted text into the same site it works fine as shown in the screenshot below.
private static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
}
Probably due to the algorithm to convert the passphrase 'password' to SecretKeySpec
This is the algorithm in AESCrypt
private static SecretKeySpec GenerateKey (final String password) throws NoSuchAlgorithmException, UnsupportedEncodingException {
final MessageDigest digest = MessageDigest.getInstance (HASH_ALGORITHM);
byte [] bytes = password.getBytes ("UTF-8");
digest.update (bytes, 0, bytes.length);
byte [] key = digest.digest ();
log ("SHA-256 key" key);
SecretKeySpec secretKeySpec = new SecretKeySpec (key, "AES");
secretKeySpec return;
}
And this is the (Java) example aesencryption.net
sha = MessageDigest.getInstance ("SHA-1");
key = sha.digest (key);
key = Arrays.copyOf (key, 16); // Use only first 128 bit
SecretKey = new SecretKeySpec (key, "AES");
The first one applies SHA256 hashing, and the second SHA-1 after completing up to 16 bytes, so the key is different.
I think you are encrypting and decrypting AES in the right way. You do not need to change anything.
But if you want to be compatible with aesencryption.net, you need to implement the same key generation algorithm. The code is not too good. I try to summarize
//Code from aesencryption.net
// Generate key
MessageDigest sha = null;
key = myKey.getBytes ("UTF-8");
sha = MessageDigest.getInstance ("SHA-1");
key = sha.digest (key);
key = Arrays.copyOf (key, 16); // Use only first 128 bit
SecretKey = new SecretKeySpec (key, "AES");
public static String encrypt (String strToEncrypt) {
Cipher cipher = Cipher.getInstance ("AES / ECB / PKCS5Padding");
cipher.init (Cipher.ENCRYPT_MODE, SecretKey);
Base64.encodeBase64String return (cipher.doFinal (strToEncrypt.getBytes ("UTF-8"))));
}
public static String decrypt (String strToDecrypt) {
Cipher cipher = Cipher.getInstance ("AES / ECB / PKCS5PADDING");
cipher.init (Cipher.DECRYPT_MODE, SecretKey);
return new String (cipher.doFinal (Base64.decodeBase64 (strToDecrypt))));
}
I can also provide my own code extracted from an Android app witch requires to store private user data. Data is ciphered with an AES key protected with an user passphrase
public static String SIMMETRICAL_ALGORITHM = "AES";
//Generate cipher key with user provided password
private static String getPassphraseSize16(String key) {
if (TextUtils.isEmpty(key)) {
return null;
}
char controlChar = '\u0014';
String key16 = key + controlChar;
if (key16.length() < 16) {
while (key16.length() < 16) {
key16 += key + controlChar;
}
}
if (key16.length() > 16) {
key16 = key16.substring(key16.length() - 16, key16.length());
}
return key16;
}
//AES cipher with passphrase
public static byte[] encrypt(byte[] message, String passphrase)
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
String passphrase16 = getPassphraseSize16(passphrase);
SecretKeySpec secretKey = new SecretKeySpec(passphrase16.getBytes(), SIMMETRICAL_ALGORITHM);
Cipher cipher = Cipher.getInstance(SIMMETRICAL_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encoded = cipher.doFinal(message);
return encoded;
}
//AES decipher with passphrase
public static byte[] decrypt(byte[] encodedMessage, String key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
String passphrase16 = getPassphraseSize16(key);
SecretKeySpec secretKey = new SecretKeySpec(passphrase16.getBytes(), SIMMETRICAL_ALGORITHM);
Cipher cipher = Cipher.getInstance(SIMMETRICAL_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte decoded[] = cipher.doFinal(encodedMessage);
return decoded;
}
I am not an expert on this so I need to defer to an expert. Is the following example a reasonably secure way to encrypt and decrypt a message (of an unknown length) for transmission a potentially insecure network (i.e. Email, HTTP requests, or other means). By "reasonably secure" I mean, would prevent a casual, or semi determined third party from reading the message.
Encrypt a message with a random AES key, and protect AES key by encrypting it with a public key.
public static String encrypt(String data, PublicKey publicKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
// Create AES secret key
Cipher aes = Cipher.getInstance("AES");
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(256);
SecretKey key = kgen.generateKey();
SecretKeySpec aeskeySpec = new SecretKeySpec(key.getEncoded(), "AES");
// Encrypt data with AES Secret key
aes.init(Cipher.ENCRYPT_MODE, aeskeySpec);
byte[] dataEncoded = aes.doFinal(data.getBytes());
// Encrypt the secret AES key with the public key
Cipher rsa = Cipher.getInstance("RSA");
rsa.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] aesKeyEncoded = rsa.doFinal(key.getEncoded());
// Output both secret AES key and data
return
Base64.getEncoder().encodeToString(aesKeyEncoded) + "~" +
Base64.getEncoder().encodeToString(dataEncoded);
}
Decrypt the AES secret key, and then decrypt the message:
public static String decrypt(String data, PrivateKey privateKey) throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException {
String[] parts = data.split("~");
// Decrypt AES secret key
byte[] encodedSecretKey = Base64.getDecoder().decode(parts[0]);
Cipher rsa = Cipher.getInstance("RSA");
rsa.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decodedSecretKey = rsa.doFinal(encodedSecretKey);
SecretKeySpec key = new SecretKeySpec(decodedSecretKey, "AES");
// Decrypt message
Cipher aes = Cipher.getInstance("AES");
aes.init(Cipher.DECRYPT_MODE, key);
byte[] decodedData = aes.doFinal(Base64.getDecoder().decode(parts[1]));
return new String(decodedData);
}
Using the above methods:
public static void main(String args[]) throws NoSuchAlgorithmException, BadPaddingException, NoSuchPaddingException, IllegalBlockSizeException, InvalidKeyException {
// Generate public/private key
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048, new SecureRandom());
KeyPair kp = generator.generateKeyPair();
System.out.println(" Public key = " + Base64.getEncoder().encodeToString(kp.getPublic().getEncoded()));
System.out.println("Private key = " + Base64.getEncoder().encodeToString(kp.getPrivate().getEncoded()));
String mytext = "test message with some test data.";
String e = encrypt(mytext, kp.getPublic());
String d = decrypt(e, kp.getPrivate());
System.out.println(" text = " + mytext);
System.out.println("Decoded text = " + d);
}
As long as you can trust the RSA public key the general idea is OK. If you just send the public key to the other side, then it is not.
You also need to protect your ciphertext by adding integrity and authenticity. You can easily do this by switching to AES / GCM mode (which is only available in Java 8, or using Bouncy Castle). Currently you are using the unsafe AES / ECB mode of encryption.
You should try and use RSA with OAEP padding, instead of PKCS#1 v1.5 padding. In general, you should not rely on default character encodings (getBytes()) or cipher modes.
So in the end: no, that's not secure. Try and use TLS is you want to avoid the many pitfalls.