I want to generate a RSA keypair in the Android Keystore. Since Android 4.3 is should be possible to generate RSA keys in the Android system Keystore.
I generate my RSA key by (works fine)
Calendar notBefore = Calendar.getInstance();
Calendar notAfter = Calendar.getInstance();
notAfter.add(1, Calendar.YEAR);
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(ctx)
.setAlias("key")
.setSubject(
new X500Principal(String.format("CN=%s, OU=%s",
"key", ctx.getPackageName())))
.setSerialNumber(BigInteger.ONE)
.setStartDate(notBefore.getTime())
.setEndDate(notAfter.getTime()).build();
KeyPairGenerator kpg;
kpg = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
kpg.initialize(spec);
KeyPair kp = kpg.genKeyPair();
publicKey = kp.getPublic();
privateKey = kp.getPrivate();
my RSA encryption looks like (works also):
public static byte[] RSAEncrypt(final byte[] plain)
throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
Cipher cipher = Cipher.getInstance("RSA");
System.out.println("RSA Encryption key: " + publicKey.getAlgorithm());
System.out.println("RSA Encryption key: " + publicKey.getEncoded());
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedBytes = cipher.doFinal(plain);
return encryptedBytes;
}
decryption:
public static byte[] RSADecrypt(final byte[] encryptedBytes)
throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
Cipher cipher1 = Cipher.getInstance("RSA");
System.out.println("RSA Encryption key: " + privateKey.getAlgorithm());
System.out.println("RSA Encryption key: " + privateKey.getEncoded());
cipher1.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedBytes = cipher1.doFinal(encryptedBytes);
return decryptedBytes;
}
In the decryption function i get the following error message(when the privateKey is encoded, and in cipher1.init()):
12-12 21:49:40.338: E/AndroidRuntime(20423): FATAL EXCEPTION: main
12-12 21:49:40.338: E/AndroidRuntime(20423): java.lang.UnsupportedOperationException: private exponent cannot be extracted
12-12 21:49:40.338: E/AndroidRuntime(20423): at org.apache.harmony.xnet.provider.jsse.OpenSSLRSAPrivateKey.getPrivateExponent(OpenSSLRSAPrivateKey.java:143)
I don't get it. Is it not possible to generate a RSA key in the Android KeyStore? Can anyone provide me with an example of generating a RSA key in the Android KeyStore and decrypt with the private Key.
Many Thanks in advance!
According to the code, I think that the OpenSSL provider prevents the private exponent to be exported when the key has been generated into the device.
#Override
public final BigInteger getPrivateExponent() {
if (key.isEngineBased()) {
throw new UnsupportedOperationException("private exponent cannot be extracted");
}
ensureReadParams();
return privateExponent;
}
Thus, you probably need to specify that you want to use the same crypto provider when retrieving the cipher instance. This provider supports these RSA ciphers:
RSA/ECB/NoPadding
RSA/ECB/PKCS1Padding
You should create an instance of the cipher this way:
Cipher cipher1 = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL");
Related
I have this java code for encryption and decryption, which I want to change/convert to Ruby code. I looked up in OpenSSL gem but dint find the "RSA/ECB/OAEPWithSHA-256AndMGF1Padding" combination available for ruby. How do I implement it?
public class EncryptDecryptService {
public String encryptRequestObject(RequestObject requestObject) throws UnsupportedEncodingException, FileNotFoundException, CertificateException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
PublicKey publicKey = getPublicKey(requestObject.getKeyFilename());
byte[] message = requestObject.getString().getBytes("UTF-8");
byte[] secret = encrypt(publicKey, message);
return Base64.encodeBase64String(secret);
}
public String decryptRequestObject(RequestObject requestObject) throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, InvalidKeyException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
PrivateKey privateKey = getPrivateKey(requestObject.getKeyFilename(), requestObject.getKeyPassword());
byte[] cipherText = Base64.decodeBase64(requestObject.getString());
byte[] decrypted = decrypt(privateKey, cipherText);
return new String(decrypted, "UTF-8");
}
private PublicKey getPublicKey(String filename) throws FileNotFoundException, CertificateException {
FileInputStream fin = new FileInputStream(filename);
CertificateFactory factory = CertificateFactory.getInstance("X.509");
X509Certificate certificate = (X509Certificate) factory.generateCertificate(fin);
PublicKey publicKey = certificate.getPublicKey();
return publicKey;
}
private PrivateKey getPrivateKey(String filename, String password) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableKeyException {
FileInputStream fin = new FileInputStream(filename);
KeyStore ks = KeyStore.getInstance("pkcs12");
ks.load(fin, password.toCharArray());
String str = ks.aliases().nextElement();
PrivateKey privateKey = (PrivateKey) ks.getKey(str, password.toCharArray());
return privateKey;
}
private byte[] encrypt(PublicKey key, byte[] plainText) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(plainText);
}
private byte[] decrypt(PrivateKey key, byte[] cipherText) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, key);
return cipher.doFinal(cipherText);
}
}
OAEP uses several parameters, including two digests, one for OAEP (i.e. for hashing the OAEP label) and one for the mask generation function (MGF1), see RFC8017, sec. 7.1.
The identifier RSA/ECB/OAEPWithSHA-256AndMGF1Padding is ambiguous and depends on the provider. For example the SunJCE provider uses SHA-256 as OAEP digest and SHA-1 as MGF1 digest, the BouncyCastle provider uses SHA-256 for both digests.
The following is an example of the encryption with the Java code and the decryption with the Ruby code (the opposite direction is analog).
On the Java side the SunJCE provider is used WLOG and the digests involved are determined:
String pubKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNvs/qUMjkfq2E9o0qn03+KJE7" +
"ASczEbn6q+kkthNBdmTsskikWsykpDPnLWhAVkmjz4alQyqw+mHYP9xhx8qUC4A3" +
"tXY0ObxANUUKhUvR7zNj4vk4t8F2nP3erWvaG8J+sN3Ubr40ZYIYLS6UHYRFrqRD" +
"CDhUtyjwERlz8KhLyQIDAQAB";
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(pubKey));
RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);
byte[] plaintext = "The quick brown fox jumps over the lazy dog".getBytes(StandardCharsets.UTF_8);
byte[] ciphertext = encrypt(publicKey, plaintext);
System.out.println("Ciphertext: " + Base64.getEncoder().encodeToString(ciphertext));
with
private static byte[] encrypt(PublicKey key, byte[] plainText) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
OAEPParameterSpec oaepParameterSpec = cipher.getParameters().getParameterSpec(OAEPParameterSpec.class);
MGF1ParameterSpec mgf1ParameterSpec = (MGF1ParameterSpec)oaepParameterSpec.getMGFParameters();
System.out.println("Provider : " + cipher.getProvider().getName());
System.out.println("OAEP-Hash : " + oaepParameterSpec.getDigestAlgorithm());
System.out.println("MGF1-Hash : " + mgf1ParameterSpec.getDigestAlgorithm());
return cipher.doFinal(plainText);
}
which corresponds to the posted encrypt() method (except for the additional output). The code produces (e.g.) the following output:
Provider : SunJCE
OAEP-Hash : SHA-256
MGF1-Hash : SHA-1
Ciphertext: WlozD9ojNRQafip41dpuuhBMe7ruH2FBWnMhbAaSuAtPDpHOUyKaAm6mO15BbvL3eTXyqfEQx29dYPJEbUr5T/WXs846PQN6g7Yv25EXGVbPCzc4aIbms76C1jP92wXNEGWMnu624Fq5W9MVXX75mfaY0Fjvrh5k/TFuO4AIxMk=
For completeness it should be mentioned that an explicit specification of the parameters is also possible with:
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
OAEPParameterSpec oaepParameterSpec = new OAEPParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-1"), PSource.PSpecified.DEFAULT);
cipher.init(Cipher.ENCRYPT_MODE, key, oaepParameterSpec);
whereby this explicit specification is the more robust alternative because of the ambivalence described above.
After the digests have been determined (either because the provider is known or explicitly with the above output) a Ruby implementation can be made.
A possible OAEP implementation for Ruby is openssl-oaep.
With this, the Ruby code for decryption can be implemented as follows:
require 'openssl'
require 'openssl/oaep'
require 'base64'
private_key =
'-----BEGIN PRIVATE KEY-----
MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAM2+z+pQyOR+rYT2
jSqfTf4okTsBJzMRufqr6SS2E0F2ZOyySKRazKSkM+ctaEBWSaPPhqVDKrD6Ydg/
3GHHypQLgDe1djQ5vEA1RQqFS9HvM2Pi+Ti3wXac/d6ta9obwn6w3dRuvjRlghgt
LpQdhEWupEMIOFS3KPARGXPwqEvJAgMBAAECgYADxGqqL7B9/pPOy3TqQuB6tuNx
4SOGm9x76onqUisoF7LhYqJR4Be/LAKHSR2PkATpKvOcMw6lDvCbtQ+j+rSK2PkN
4iDi1RYqbLUbZBS8vhrgU0CPlmgSSp1NBsqMK9265CaJox3frxmBK1yuf22RboIK
pqOzcluuA4aqLegmwQJBAP0+gM/tePzx+53DrxpYQvlfi9UJo7KeqIFL8TjMziKt
EaRGeOZ6UX/r6CQHojYKnNti7pjAwonsdwCTcv1yy7sCQQDP+/ww49VFHErON/MO
w5iYCsrM5Lx+Yc2JAjetCDpkMrRT92cgQ0nxR5+jNeh+gE2AmB9iKlNxsHJoRaPQ
lBRLAkEAl9hiZEp/wStXM8GhvKovfldMAPFGtlNrthtTCDvFXgVoDpgy5f9x3sIU
74WkPcMfSmyHpA/wlcKzmCTRTicHAQJBALUjq7MQ2tAEIgqUo/W52I6i55mnpZsU
pyOqcL8cqW5W0sNGd+SbdizTym8lJkX2jIlw8/RVFLOxjxLNhCzGqx0CQQDeUMnw
7KGP3F7BnbsXCp64YDdihzSO5X/Mfwxw6+S/pyKZ0/X4uwt24kZuoDnFzGWJYlea
sDQC6enIru+ne5es
-----END PRIVATE KEY-----'
key = OpenSSL::PKey::RSA.new(private_key)
label = ''
md_oaep = OpenSSL::Digest::SHA256
md_mgf1 = OpenSSL::Digest::SHA1
cipher_text_B64 = 'WlozD9ojNRQafip41dpuuhBMe7ruH2FBWnMhbAaSuAtPDpHOUyKaAm6mO15BbvL3eTXyqfEQx29dYPJEbUr5T/WXs846PQN6g7Yv25EXGVbPCzc4aIbms76C1jP92wXNEGWMnu624Fq5W9MVXX75mfaY0Fjvrh5k/TFuO4AIxMk='
cipher_text = Base64.decode64(cipher_text_B64)
plain_text = key.private_decrypt_oaep(cipher_text, label, md_oaep, md_mgf1)
print(plain_text) # The quick brown fox jumps over the lazy dog
with the original plaintext as output.
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.
I'm trying to decrypt a string using RSA and my private key (size 4096). I'm getting the error:
console show error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag
this es my code:
public String decrypt(String texte ) throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeySpecException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
String stringKey = "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";
byte[] keyBytes = stringKey.getBytes();
String painText=texte;
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
Cipher cipher = Cipher.getInstance(CIPHER_RSA_WITH_PADDING, PROVIDER);
cipher.init(DECRYPT_MODE,privateKey);
byte[] encryptedMessageBytes = Base64.decode(painText.getBytes().toString(),BASE64_FLAG);
return new String(cipher.doFinal(encryptedMessageBytes,0,encryptedMessageBytes.length));
}
the error is thrown out here:
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
I had a similar error when attempting to decode an RSA private key (2048 bits), but only on Marshmallow and older. Nougat and newer worked fine. This is because of the different Security Provider implementations on the different versions of Android. To work around this, I'm using the SpongyCastle provider.
I added the following line to my build.gradle:
implementation 'com.madgag.spongycastle:prov:1.58.0.0'
and the following line to my Application class's onCreate() method
Security.insertProviderAt(org.spongycastle.jce.provider.BouncyCastleProvider(), 1)
For completeness, here's my RSA decryption code:
fun decryptRsa(
cipherText: String,
key: String
): String {
val keySpec = PKCS8EncodedKeySpec(Base64.decode(key, Base64.DEFAULT))
val factory = KeyFactory.getInstance("RSA")
val privateKey: PrivateKey = factory.generatePrivate(keySpec) as RSAPrivateKey
val cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING")
cipher.init(Cipher.DECRYPT_MODE, privateKey)
val encryptedBytes: ByteArray = Base64.decode(cipherText, Base64.DEFAULT)
return String(cipher.doFinal(encryptedBytes))
}
I am trying to implement AES/GCM/NoPadding encryption and decryption in JAVA .. the key used is a shared key from the public key of the receiver and the private key of the sender (ECDH).. encryption works well (with and without iv). However, I am unable to decrypt...
I get the exception: javax.crypto.BadPaddingException: mac check in GCM failed
public static String encryptString(SecretKey key, String plainText) throws NoSuchProviderException, NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
//IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");//AES/ECB/PKCS5Padding //"AES/GCM/NoPadding", "BC"
byte[] plainTextBytes = plainText.getBytes("UTF-8");
byte[] cipherText;
//cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
cipher.init(Cipher.ENCRYPT_MODE, key);
return new String(Base64.getEncoder().encode(cipher.doFinal(plainTextBytes)));
}
public static String decryptString(SecretKey key, String
cipherText) throws NoSuchProviderException,
NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, InvalidAlgorithmParameterException,
IllegalBlockSizeException, BadPaddingException,
UnsupportedEncodingException, ShortBufferException {
Key decryptionKey = new SecretKeySpec(key.getEncoded(),
key.getAlgorithm());
IvParameterSpec ivSpec = new IvParameterSpec(ivString.getBytes("UTF-8"));
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");//AES/GCM/NoPadding", "BC");
cipher.init(Cipher.DECRYPT_MODE, decryptionKey, ivSpec);
return new String (Base64.getEncoder().encode(cipher.doFinal(Base64.getDecoder().decode(cipherText.getBytes()))));
}
You must use exactly the same IV for encryption and decryption of the same ciphertext and it must be different for each encryption that produces different ciphertexts. The IV is not secret, so you can send it along with the ciphertext. Usually, it is simply prepended to the ciphertext and sliced off before decryption.
You need to supply an instance of GCMParameterSpec (which includes the IV) for both of the Cipher.init calls. As has already been pointed out, the IV has to be the same for both encryption and decryption, and must be unique.
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.