Encrypting with EC key: what's the use of IEKeySpec? - java

I try to encrypt/decrypt messages using a EC key. I created the key ussing openssl:
openssl ecparam -name prime256v1 -genkey -noout -out secp256r1-key.pem
openssl ec -in secp256r1-key.pem -pubout -out secp256r1-pub.pem
openssl req -new -key secp256r1-key.pem -x509 -nodes -days 99999 -out secp256r1-cert.pem
Created a java spring boot app using bouncy castle library for encryption:
public String encrypt_1(final String msg, PublicKey publicKey) throws Exception {
final IESCipher c1 = new org.bouncycastle.jcajce.provider.asymmetric.ec.IESCipher.ECIES();
final Cipher cipher;
cipher = Cipher.getInstance("ECIES");
final byte[] msgBytes = msg.getBytes("UTF-8");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
final byte[] encryptedBytes = cipher.doFinal(msgBytes);
return Base64.getEncoder().encodeToString(encryptedBytes);
}
public String decrypt_1(final String msgEncrypted, PrivateKey privateKey) throws Exception {
final Cipher cipher = Cipher.getInstance("ECIES");
final byte[] msgBytes = Base64.getDecoder().decode(msgEncrypted);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
final byte[] decryptedJwtBytes = cipher.doFinal(msgBytes);
return new String(decryptedJwtBytes, StandardCharsets.US_ASCII);
}
This works as I have output. However when I see other people implementing ECIES in java they use IESParameterSpec and IEKeySpec with as input both public and private key of both own and foreign key (as it is asymetric). An example can be found here: https://gist.github.com/amrishodiq/9821413
or on stackOverflow:
ECC algorithm key mismatch
They do something like:
...
IESParameterSpec param = new IESParameterSpec(d, e, 256);
// decrypt the text using the private key
bcipher.init(Cipher.DECRYPT_MODE, new IEKeySpec(tpPrivateKey, ownPublicKey), param);
final byte[] decryptedBytes = bcipher.doFinal(cipherTextBytes);
...
I can't get that code to work, but I want to know if this is more secure and why both keys are used at the same time during encryption/decryption.

You're missing some fundamental understanding here.
You cannot encrypt per se with elliptic curves (ElGamal with an elliptic curve group is an exception, but not used in practice).
"Encryption" in the context of elliptic curves is really the following process:
Knowing that an EC private key is a large number, and an EC public key is a (x,y) point on the curve's Cartesian plane.
Generating a shared symmetric key via elliptic curve Diffie Hellman.
This ECDH process requires input from two parties, where each party effectively calculates their private key * public point A * public point B.
This calculation will yield another shared secret point on the curve, next the x co-ordinate component of this new point is used and passed through a hashing function to generate uniform key material.
Finally, you use this key with a strong symmetric cipher, preferably something like ChaCha20-Poly1305.
In summary, with EC crypto, first we do a little dance to agree a key, then we can use any symmetric cipher we like.
It's that simple, don't get mired in unnecessary detail. Also move to a different library like libsodium if possible, BC is noisy.
Finally,
byte[] d = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
byte[] e = new byte[] { 8, 7, 6, 5, 4, 3, 2, 1 };
IESParameterSpec param = new IESParameterSpec(d, e, 256);
The above "derivation and encoding vectors" are worthless.

Related

How do I decrypt a Java-DES-encrypted message using openssl?

Since the question title is self-explaining, please consider the following code:
private static final String ALGORITHM = "DES";
private static final String MESSAGE = "This is an extremely secret message";
private static final byte[] key = { 0, 1, 2, 3, 4, 5, 6, 7 };
...
// Do encryption
final Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(ENCRYPT_MODE, new SecretKeySpec(key, ALGORITHM));
final byte[] encrypted = cipher.doFinal(MESSAGE.getBytes());
// Copy the encrypted message to a file
final InputStream inputStream = new ByteArrayInputStream(encrypted);
final OutputStream outputStream = new FileOutputStream("___SECRET");
copy(inputStream, outputStream);
Now I'm trying to decrypt the ___SECRET file with the following command:
openssl enc -d -des -K 0001020304050607 -iv 0 -in ___SECRET -out ___OPEN
which results in:
bad decrypt
3073636028:error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt:evp_enc.c:539:
just decrypting the very first block (8 bytes) leaving the rest in trash state (OEM encoding):
This is MЕ$S6%#╢Т√°ў╝°╢]∙iь
What am I doing wrong and how do I decrypt the encrypted message using openssl?
On Java you use DES in ECB mode and on OpenSSL you use DES in CBC mode (IV is present).
This is a significant difference as in CBC the blocks are chained - therefore the first block is decrypted correctly but all following blocks are scrambled.
You can change the Java part to use "DES/CBC" mode instead and provide an IV or change the openssl part and use -des-ecb instead of -des.

BouncyCastle Encoding differences

I'm trying to calculate the hash of a public key to match what ApplePay is giving me. I can't get BouncyCastle in C# to give me the public key in the right format so I can hash it. However the Java version that I'm porting works just fine. OpenSSL also gives the right response, but I'd prefer not to have to deal with OpenSSL as well as BC to do this.
Java: (works)
ECPublicKey key = (ECPublicKey) certificate.getPublicKey();
byte[] keyBytes = key.getEncoded();
MessageDigest messageDigest = MessageDigest.getInstance("SHA256", PROVIDER_NAME);
byte[] keyHash = messageDigest.digest(keyBytes);
String base64keyBytes = new String(Base64.encode(keyBytes));
//base64keyBytes == MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+4wQWWRnPqGlsncZX17t0CfLOl6u
// 68aXUsqnzlIcpCdDukHhxibd2MjHPFGpnK3ZKdHxIFh+NBQvGssM5ncm1g==
// line break added for readability
OpenSSL: (works)
openssl x509 -noout -pubkey -in <file> -inform der
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+4wQWWRnPqGlsncZX17t0CfLOl6u
68aXUsqnzlIcpCdDukHhxibd2MjHPFGpnK3ZKdHxIFh+NBQvGssM5ncm1g==
-----END PUBLIC KEY-----
C#: (keyBytes are wrong)
SubjectPublicKeyInfo pubinfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(certificate.GetPublicKey());
byte[] keyBytes = pubinfo.GetEncoded();
byte[] keyHash = DigestUtilities.CalculateDigest("SHA_256", keyBytes);
string keyBytesEncoded = Convert.ToBase64String(keyBytes);
/*keyBytesEncoded == MIIBKjCB4wYHKoZIzj0CATCB1wIBATAsBgcqhkjOPQEBAiEA/////wAAAAEAAA
AAAAAAAAAAAAD///////////////8wWwQg/////wAAAAEAAAAAAAAAAAAAAAD/
//////////////wEIFrGNdiqOpPns+u9VXaYhrxlHQawzFOw9jvOPD4n0mBLAx
UAxJ02CIbnBJNqZnjhE50mt4GffpAEIQNrF9Hy4SxCR/i85uVjpEDydwN9gS3r
M6D0oTlF2JjClgIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8YyVRAg
EBA0IABPuMEFlkZz6hpbJ3GV9e7dAnyzperuvGl1LKp85SHKQnQ7pB4cYm3djI
xzxRqZyt2SnR8SBYfjQULxrLDOZ3JtY=
line break added for readability */
What is the right way to port the GetEncoding?
I know it's the key data that wrong because when I manually force in the data from openssl I get the right hash.
EDIT: Added the out puts.
I think it would just be:
byte[] keyBytes = certificate.CertificateStructure.SubjectPublicKeyInfo.GetDerEncoded()

c# RSA encrypt with private key

Encryption and Decryption successful when encrypt with public key and decrypt with private key :
C# encryption with public key(Successful)
public string EncryptData(string data) {
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(xml); //public key
var cipher = rsa.Encrypt(Encoding.UTF8.GetBytes(data), false);
return Convert.ToBase64String(cipher );
}
Java decryption with private key(Successful)
public static void decrypt() throws Exception{
byte[] modulusBytes = Base64.getDecoder().decode(mod);
byte[] dByte = Base64.getDecoder().decode(d);
BigInteger modulus = new BigInteger(1, (modulusBytes));
BigInteger exponent = new BigInteger(1, (dByte));
RSAPrivateKeySpec rsaPrivKey = new RSAPrivateKeySpec(modulus, exponent);
KeyFactory fact = KeyFactory.getInstance("RSA");
PrivateKey privKey = fact.generatePrivate(rsaPrivKey);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privKey);
byte[] cipherData = Base64.getDecoder().decode(cipherByte);
byte[] plainBytes = cipher.doFinal(cipherData);
System.out.println(new String(plainBytes));
}
Problem is Here
When c# encrypt with private key and java decrypt with public key bad padding error occur:
C# encryption with private key(Fail)
public stringEncryptData(string data) {
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(xml); //private key
var cypher = rsa.Encrypt(Encoding.UTF8.GetBytes(data), false);
return Convert.ToBase64String(cypher);
}
java decryption with public key (Fail)
public static void decryptPublic() throws Exception{
byte[] modulusBytes = Base64.getDecoder().decode(mod);
byte[] expBytes = Base64.getDecoder().decode(exp);
BigInteger modulus = new BigInteger(1, (modulusBytes));
BigInteger exponent = new BigInteger(1, (expBytes));
RSAPublicKeySpec pubKey = new RSAPublicKeySpec(modulus, exponent);
KeyFactory fact = KeyFactory.getInstance("RSA");
PublicKey publicKey = fact.generatePublic(pubKey);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, publicKey );
byte[] cipherData = Base64.getDecoder().decode(cipherByte);
byte[] plainBytes = cipher.doFinal(cipherData);
System.out.println(new String(plainBytes));
}
I understand public key should use to do encryption and private key for decryption.But in my situation, i need to sent out public key to mutiple clients for decryption on a text encrypted by its private key. Text should be non readable by others except client.
Can anyone see what problem on my code, or suggest a better solution to my problem.
RSA encryption is only secure if a (secure) padding scheme is being used. RSA encryption schemes have been specified in PKCS#1 standards by RSA laboratories (now part of EMC2). These have been copied into RFC, such as RFC 3447: Public-Key Cryptography Standards (PKCS) #1: RSA Cryptography Specifications Version 2.1.
For the purposes of this document, an encryption scheme consists of
an encryption operation and a decryption operation, where the
encryption operation produces a ciphertext from a message with a
recipient's RSA public key, and the decryption operation recovers the
message from the ciphertext with the recipient's corresponding RSA
private key.
So encryption with a private key is an undefined operation.
So what to do now:
securely distribute private keys instead of public keys
generate key pairs and securely transport the public key to the sender
if you require authentication/integrity instead of confidentiality, use signature generation instead of encryption
And, whatever you do, read into Public Key Infrastructure (PKI). It's a far stretching subject that you need to understand before you can apply it.
Encrypting with the private key/decrypting with the public key is a legitimate operation in RSA, however it is not used to protect data, it is instead used to authenticate the source of the data and its integrity. In this context the encryption operation is more usually called "signing".
Encrypting using the private key to protect data as you describe is insecure and so the fact that it is not easily done is likely intentional and intended to prevent incorrect use of the algorithm.
Distributing your private key to clients as suggested in the comments is also unwise since you have no control over who they may pass the key onto (accidentally or otherwise).
If you wish to encrypt data so that it can be decrypted by multiple distinct parties, then you should have each of them provide you with their own public key, and use that to encrypt the data separately for each client.

Serialize and Deserialize an RSA public key

KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(1024);
KeyPair kp = kpg.genKeyPair();
Key publicKey = kp.getPublic();
Key privateKey = kp.getPrivate();
I want to create just the public key from a byte[].
I have tried this as an experiment:
publicKey = new SecretKeySpec(publicKey.getEncoded(), publicKey.getAlgorithm());
But decryption using that key then fails.
I have also tried serializing the key with ObjectOutputStream, but serialization fails.
java.io.NotSerializableException: org.apache.harmony.xnet.provider.jsse.OpenSSLKey
I read here that I can't use SecretKeySpec with RSA.
so as long as you are talking of a SecretKey and not an RSA or DSA key then you don't have to go through any contortions involving KeyGenerator or the like.
Anyone know how to perform these contortions or a way of doing this.
Asymmetric keys like those from RSA are usually stored in X509 format. Therefor you can use X509EncodedKeySpecinstead.
A simple example is already in the Java 7 JavaDoc (just replace DSA with RSA):
http://docs.oracle.com/javase/7/docs/api/java/security/KeyFactory.html
X509EncodedKeySpec bobPubKeySpec = new X509EncodedKeySpec(bobEncodedPubKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey bobPubKey = keyFactory.generatePublic(bobPubKeySpec);
If you need to deserialize the private from byte[], I've found that you must use PKCS8EncodedKeySpec.
Depends on what do you want to do with the serialized representation. If the consumer is no one but your own program, feel free to roll your own implementation. A public RSA key consists of two integers - exponent and modulus. Modulus is large - around 1024 bits, exponent is typically on the order of 17 bits. Both are available as BigInteger objects if you cast your public key object to RSAPublicKey.
So, to recap:
RSAPublicKey publicKey = (RSAPublicKey)kp.getPublic();
return publicKey.getModulus().toString() + "|" +
publicKey.getPublicExponent().toString();
That's sufficient to restore the key. To deserialize:
String []Parts = MyKeyString.split("\\|");
RSAPublicKeySpec Spec = new RSAPublicKeySpec(
new BigInteger(Parts[0]),
new BigInteger(Parts[1]));
return KeyFactory.getInstance("RSA").generatePublic(Spec);
If the key needs to be passed to third party software, you better serialize to a standard format - PEM or DER.
I use the following code to convert a PubliKey to PEM Base64 format
String publicKeyString = javax.xml.bind.DatatypeConverter.printBase64Binary(publicKey.getEncoded());
I hope it helps.

RSA decryption using modulus and exponent

My task: I have encrypted (RSA) data and public key as modulus and exponent. I have to write decryption code.
My problem with it: My implementation doesn't work ;) As far as I know philosophy is simple "open text" == rsa(public_key, rsa(private_key, "open text")) Edit: Exactly my assumption was wrong (Assumption is mother of all fu..ups ;) ). It should be "open text" == rsa(private_key, rsa(public_key, "open text")) because in RSA, public key is used for encryption and private for decryption.
I assumed that I can have public key which doesn't correspond to private key using during encryption so for tests I created own keys in such way:
openssl genrsa -des3 -out server.key 1024
openssl req -new -key server.key -out server.csr
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
I got public key modulus and exponent using command:
openssl x509 -in server.crt -text
For encryption testing I'm using code
//Reads private key from file
//StringPasswordFinder is my tmp implementation of PasswordFinder
PEMReader pemReader = new PEMReader(new FileReader("/path/to/server.key"), new StringPasswordFinder());
KeyPair keyPair = (KeyPair) pemReader.readObject();
PrivateKey pk = keyPair.getPrivate();
//text for encryption
String openText = "openText";
//encryption
Cipher rsaCipher = Cipher.getInstance("RSA", "BC");
rsaCipher.init(Cipher.ENCRYPT_MODE, pk);
byte[] encrypted = rsaCipher.doFinal(openText.getBytes("utf-8"));
And for decryption of encrypted text I use code
//modulus hex got using openssl
byte[] modulus = Hex.decodeHex("very long hex".toCharArray());
//exponent hex got using openssl
byte[] exponent = Hex.decodeHex("010001".toCharArray());
//initialization of rsa decryption engine
RSAEngine rsaEngine = new RSAEngine();
rsaEngine.init(false, new RSAKeyParameters(false, new BigInteger(modulus), new BigInteger(exponent)));
//input - encrypted stream
ByteArrayInputStream bais = new ByteArrayInputStream(encrypted);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//reading blocks from the input stream and decrypting them
int bytesRead = 0;
byte[] block = new byte[rsaEngine.getInputBlockSize()];
while ((bytesRead = bais.read(block)) > -1) {
baos.write(rsaEngine.processBlock(block, 0, bytesRead));
}
//dispalying decrypted text
System.out.println(new String(baos.toByteArray(), "utf-8"));
And after all displayed text is not. Can anybody show me where I'm wrong?
Edit: Summing up this problem has no solution. Because it's not possible encrypt message using private key and later decrypt it using public one. At general I mixed up encryption with signing message and decryption with verification. Because during making signature private key is used and public is used during verification. Btw, MByD thx for important clue.
I am not so familiar with java libraries for RSA, the times I tried to implement RSA in java was to build all calculations by myself, but if I understood you correct, I see 2 problems:
the data should be encrypted with the public key and decrypted with private key, not the other way around (since everyone with public key will be able to decrypt it...)
the public key should match the private key, otherwise, anyone with any private key will be able to decrypt data encrypted with any public key...
Also, for very long data, you should not use public key encryption. Instead, encrypt the data in some other algorithm (RC4, AES, etc.) and encrypt the key in RSA (similar to PGP approach)

Categories

Resources