Good day,
There is another third party that need my web application to send them some data in encrypt format. Thus they send me some guide to do so, however, I am not familiar with it, I am trying to google around but looks like I am google wrong way.
The guide is something as follow:
Run openssl command to generate a privatekey:
openssl ecparam -name prime256v1 -genkey -out myprivate.pem
After run this command, I output a priv.pem file, and I saw inside got some key end with '==', which is as follow:
-----BEGIN EC PARAMETERS-----
BggqhkjOPQMBBw==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEILefWfeuZOgnbDlxpwo3uQ2xQXfhXHUPTS+vKzvVZdCToAoGCCqGSM49
AwEHoUQDQgAE4MeQspGRJ1qdpweBfiaT5P84alZdga1f7mSpa5HqXTH58u0ZWJUQ
J7ToU/bUOPITh4FX07AV6wrgFCmwtUenDQ==
-----END EC PRIVATE KEY-----
Second one is run openssl command to generate the public key, and then send them:
openssl ec -in myprivate.pem -pubout -out mypublic.pem
Convert the private key to pkcs8 format:
openssl pkcs8 -topk8 -nocrypt -in myprivate.pem -out mypkcs8.pem
The third party will give me a public key in string format, then ask me to generate a secret key, and provide me some java code as follow:
first is to generate secret key and second one is encrypt:
public static SecretKey generateSharedSecret(PrivateKey privateKey,
PublicKey publicKey) {
try {
KeyAgreement keyAgreement = KeyAgreement.getInstance( "ECDH" );
keyAgreement.init( privateKey );
keyAgreement.doPhase( publicKey, true );
SecretKeySpec key = new SecretKeySpec(
keyAgreement.generateSecret( ), "AES" );
return key;
} catch ( Exception e ) {
// TODO Auto-generated catch block
e.printStackTrace( );
return null;
}
}
public static String encryptString(SecretKey key, String plainText) {
try {
String myIv = "Testing # IV!";
byte[] iv = myIv.getBytes( "UTF-8" );
IvParameterSpec ivSpec = new IvParameterSpec( iv );
Cipher cipher = Cipher.getInstance( "AES / CBC / PKCS5Padding" );
byte[] plainTextBytes = plainText.getBytes( "UTF-8" );
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 bytesToHex( cipherText );
} catch ( Exception e ) {
e.printStackTrace( );
return null;
}
}
and also the bytes to hex string method:
public static String bytesToHex(byte[] byteArray) {
StringBuffer hexStringBuffer = new StringBuffer( );
for ( int i = 0; i < byteArray.length; i++ ) {
hexStringBuffer.append( String.format( "%02X", byteArray[ i ] ) );
}
return hexStringBuffer.toString( );
}
I have self gen a private key and also a public key by using openssl command, but the 4th step telling me that they will give me a public key as well, thus I am not understand, which public key should I use.
And also, how can I convert a String into java PrivateKey and PublicKey object?
* add on *
I try to convert the der file to java PublicKey object, it looks work. Before this, I convert the pem to der using openssl command:
openssl pkey -pubin -in ecpubkey.pem -outform der -out ecpubkey.der
Here is the java code:
File f = new File("/home/my/Desktop/key/ecpubkey.der");
FileInputStream fis = new FileInputStream(f);
DataInputStream dis = new DataInputStream(fis);
byte[] keyBytes = new byte[(int) f.length()];
dis.readFully(keyBytes);
dis.close();
KeyFactory fact = KeyFactory.getInstance("EC");
PublicKey theirpub = fact.generatePublic(new X509EncodedKeySpec(keyBytes));
However, I am hitting java.security.spec.InvalidKeySpecException: java.io.IOException: insufficient data when I try to convert der file to java PrivateKey object, the following is what I did:
openssl ecparam -name prime256v1 -genkey -out priv.pem
openssl pkcs8 -topk8 -nocrypt -in priv.pem -outform der -out priv.der
And the following is my java code:
File f2 = new File("/home/my/Desktop/key/priv.der");
FileInputStream fis2 = new FileInputStream(f2);
DataInputStream dis2 = new DataInputStream(fis2);
byte[] keyBytes2 = new byte[(int) f.length()];
dis2.readFully(keyBytes2);
dis2.close();
KeyFactory fact2 = KeyFactory.getInstance("EC");
PrivateKey pKey = fact2.generatePrivate( new PKCS8EncodedKeySpec(keyBytes2) ); // this line hit insufficient data
Diffie-Hellman is well-explained in wikipedia -- and probably some of the hundreds of Qs here, and crypto.SX and security.SX, about it, but I can't easily find which. In brief:
you generate a keypair, keep your privatekey, and provide your publickey to the other party
the other party does the same thing (or its reflection): generate a keypair, keep their privatekey, and provide their publickey to you
you use your privatekey and their publickey to compute the 'agreement' value
they similarly use their privatekey and your publickey to compute the same 'agreement' value. This is also called a shared secret, because you and the other party know it, but anyone eavesdropping on your traffic does not.
The 'provide' in that synopsis omits a lot of very important details. It is vital that when you provide your publickey to the other party they actually get your publickey and not a value altered or replaced by an adversary, and similarly when they provide their publickey to you it is vital you get the real one and not a modified or fake one. This is where actual DH systems mostly break down, and the fact you mention none of the protections or complications needed here suggests your scheme will be insecure and easily broken -- if used for anything worth stealing.
Note you should NEVER disclose or 'send' your privatekey to anyone, and they should similarly not disclose theirs. That's the main basis for public-key (or 'asymmetric') cryptography to be of any value or use at all.
There are numerous ways that keys can be represented, but only some are relevant to you.
Public keys are often represented either in
the ASN.1 structure SubjectPublicKeyInfo defined in X.509 and more conveniently in PKIX, primarily in rfc5280 #4.1 and #4.1.2.7 and rfc3279 2.3, encoded in DER, which has the limitation that many of the bytes used in this encoding are not valid characters and cannot be correctly displayed or otherwise manipulated and sometimes not transmitted or even stored; or
that same ASN.1 DER structure 'wrapped' in 'PEM' format, which converts the troublesome binary data to all displayable characters in an easily manipulable form. PEM format was originally created for a secure-email scheme call Privacy Enhanced Mail which has fallen by the wayside, replaced by other schemes and technologies, but the format it defined is still used. The publickey PEM format was recently re-standardized by rfc7468 #13 (which as you see referenced rfc5280).
OpenSSL supports both of these, but the commandline utility which you are using mostly defaults to PEM -- and since you need to convey your key to 'them', and they need to convey their key to you, PEM may well be the most reliable and/or convenient way of doing so. (Although other formats are possible, if you and they agree -- and if they require something else you'll have to agree for this scheme to work at all.)
Java directly supports only DER, thus assuming you receive their publickey in SPKI PEM, to use it in Java you need to convert it to DER. You can either do this in OpenSSL
openssl pkey -pubin -in theirpub.pem -outform der -out theirpub.der
and then read the DER into a Java crypto KeyFactory:
byte[] theirpubder = Files.readAllBytes(Paths.get(whatever));
KeyFactory fact = KeyFactory.getInstance("EC");
PublicKey theirpub = fact.generatePublic(new X509EncodedKeySpec(theirpubder));
// can downcast to ECPublicKey if you want to be more specific
Alternatively you can have Java convert the PEM which isn't too hard; there are several variations but I like:
String theirpubpem = new String(Files.readAllBytes(Paths.get(whatever)));
// IN GENERAL letting new String(byte[]) default the charset is dangerous, but PEM is OK
byte[] theirpubder = Base64.getMIMEDecoder().decode(theirpubpem.replaceAll("-----[^\\n]*\\n","") );
// continue as for DER
For private keys
there are significantly more representations, but only one (or two-ish) that Java shares with OpenSSL. Since you only need to store the private key locally and not 'send' it, PEM may not be needed; if so you can just add -outform der to your pkcs8 -topk8 -nocrypt command, adjusting the name appropriately, and read the result directly in a Java KeyFactory in the same fashion as above except with PKCS8EncodedKeySpec and generatePrivate and [EC]PrivateKey. If you do want to store it in (PKCS8-clear) PEM, you can also combine the above.
Using the DH agreement value directly as a symmetric cipher (e.g. AES) key is nonstandard and generally not considered good practice, although for ECDH with prime256v1 (aka secp256r1 or P-256) it is technically possible. AFAIK all good standards use a key-derivation step (aka Key Derivation Function or KDF) in between. Since you haven't shown us their 'guide' I can't say if this is correct -- for at least small values of correct.
To be sure you know, using CBC with a fixed IV more than once for the same key (which in this case is the same DH result) is insecure. I assume 'Testing' means you plan to replace it with something better.
Also FYI you don't need to use the full complication of the Cipher.init,update,doFinal API. When the data is small enough to fit in memory, as here, you can just do:
cipher.init(ENCRYPT_MODE, key, parms);
byte[] encrypted = cipher.doFinal (plainbytes);
// or since you want to hexify it
... bytesToHex (cipher.doFinal (plainbytes)) ...
Finally because Java byte is signed, your bytesToHex will output almost exactly half of all bytes with FFFFFF prefixed. This is very unusual, and phenomenally ugly, but again I don't know if it is 'correct' for you.
Base on dave_thompson_085 explanation and code, I manage to create my java PublicKey and Privatekey with following:
public static PublicKey getPublicKey(String filename) throws IOException, GeneralSecurityException {
String publicKeyPEM = getKey(filename);
return getPublicKeyFromString(publicKeyPEM);
}
private static String getKey(String filename) throws IOException {
// Read key from file
String strKeyPEM = "";
BufferedReader br = new BufferedReader(new FileReader(filename));
String line;
while ((line = br.readLine()) != null) {
strKeyPEM += line + "\n";
}
br.close();
return strKeyPEM;
}
public static PublicKey getPublicKeyFromString(String key) throws IOException, GeneralSecurityException {
String publicKeyPEM = key;
publicKeyPEM = publicKeyPEM.replace("-----BEGIN PUBLIC KEY-----\n", "");
publicKeyPEM = publicKeyPEM.replace("-----END PUBLIC KEY-----", "");
BASE64Decoder b = new BASE64Decoder();
byte[] encoded = b.decodeBuffer(publicKeyPEM);
KeyFactory kf = KeyFactory.getInstance("EC");
PublicKey pubKey = (PublicKey) kf.generatePublic(new X509EncodedKeySpec(encoded));
return pubKey;
}
and this is for private key
public static PrivateKey getPrivateKey(String filename) throws IOException, GeneralSecurityException {
String privateKeyPEM = getKey(filename);
return getPrivateKeyFromString(privateKeyPEM);
}
public static PrivateKey getPrivateKeyFromString(String key) throws IOException, GeneralSecurityException {
String privateKeyPEM = key;
privateKeyPEM = privateKeyPEM.replace("-----BEGIN PRIVATE KEY-----\n", "");
privateKeyPEM = privateKeyPEM.replace("-----END PRIVATE KEY-----", "");
BASE64Decoder b = new BASE64Decoder();
byte[] encoded = b.decodeBuffer(privateKeyPEM);
KeyFactory kf = KeyFactory.getInstance("EC");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
PrivateKey privKey = (PrivateKey) kf.generatePrivate(keySpec);
return privKey;
}
Many thanks to #dave_thompson_085 explanation.
I have used the below OpenSSL code to do an AES encryption which is decrypting successfully in the Tax website
openssl rand 48 > 48byterandomvalue.bin
hexdump /bare 48byterandomvalue.bin > 48byterandomvalue.txt
set /a counter=0
for /f "tokens=* delims= " %%i in (48byterandomvalue.txt) do (
set /a counter=!counter!+1
set var=%%i
if "!counter!"=="1" (set aes1=%%i)
if "!counter!"=="2" (set aes2=%%i)
if "!counter!"=="3" (set iv=%%i)
)
set result1=%aes1:~0,50%
set result1=%result1: =%
set result2=%aes2:~0,50%
set result2=%result2: =%
set aeskey=%result1%%result2%
set initvector=%iv:~0,50%
set initvector=%initvector: =%
openssl aes-256-cbc -e -in PAYLOAD.zip -out PAYLOAD -K %aeskey% -iv %initvector%
openssl rsautl -encrypt -certin -inkey test_public.cer -in
48byterandomvalue.bin -out 000000.00000.TA.840_Key
But I wanted to do the same this in Java as part of migration, so i used the javax.crypto and java.security libraries but the decryption is failing when I upload the file on the Tax website
//creating the random AES-256 secret key
SecureRandom srandom = new SecureRandom();
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
SecretKey secretKey = keyGen.generateKey();
byte[] aesKeyb = secretKey.getEncoded();
//creating the initialization vector
byte[] iv = new byte[128/8];
srandom.nextBytes(iv);
IvParameterSpec ivspec = new IvParameterSpec(iv);
byte[] encoded = Files.readAllBytes(Paths.get(filePath));
str = new String(encoded, StandardCharsets.US_ASCII);
//fetching the Public Key from certificate
FileInputStream fin = new FileInputStream("test_public.cer");
CertificateFactory f = CertificateFactory.getInstance("X.509");
X509Certificate certificate = (X509Certificate)f.generateCertificate(fin);
PublicKey pk = certificate.getPublicKey();
//encrypting the AES Key with Public Key
Cipher RSACipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
RSACipher.init(Cipher.ENCRYPT_MODE, pk);
byte[] RSAEncrypted = RSACipher.doFinal(aesKeyb);
FileOutputStream out = new FileOutputStream("000000.00000.TA.840_Key");
out.write(RSAEncrypted);
out.write(iv);
out.close();
Also, the AES key generated in java is different from the one generated via openssl. Can you guys please help.
EDIT 1:
Below is the code for AES Encrpytion used:
Cipher AESCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
AESCipher.init(Cipher.ENCRYPT_MODE, secretKey, ivspec);
byte[] AESEncrypted = AESCipher.doFinal(str.getBytes("UTF-8"));
String encryptedStr = new String(AESEncrypted);
The data that script and Java-code encrypt with RSA differ:
The script generates a random 48-bytes-sequence and stores it in the file 48byterandomvalue.bin. The first 32 bytes are used as AES key, the last 16 bytes as IV. Key and IV are used to encrypt the file PAYLOAD.zip with AES-256 in CBC-mode and store it as file PAYLOAD. The file 48byterandomvalue.bin is encrypted with RSA and stored as file 000000.00000.TA.840_Key.
In the Java-code, a random 32-bytes AES key and a random 16-bytes IV are generated. Both are used to perform the encryption with AES-256 in CBC-mode. The AES key is encrypted with RSA, concatenated with the unencrypted IV and the result is stored in the file 000000.00000.TA.840_Key.
The content of the file 000000.00000.TA.840_Key is different for script and Java-code. For the Java-code to generate the file 000000.00000.TA.840_Key with the script-logic, the unencrypted AES key must be concatenated with the unencrypted IV and this result must be encrypted with RSA:
...
//byte[] aesKeyb byte-array with random 32-bytes key
//byte[] iv byte-array with random 16-bytes iv
byte[] key_iv = new byte[aesKeyb.length + iv.length];
System.arraycopy(aesKeyb, 0, key_iv, 0, aesKeyb.length);
System.arraycopy(iv, 0, key_iv, aesKeyb.length, iv.length);
...
byte[] RSAEncrypted = RSACipher.doFinal(key_iv);
FileOutputStream out = new FileOutputStream("000000.00000.TA.840_Key");
out.write(RSAEncrypted);
out.close();
...
Note: The IV doesn't have to be secret and therefore doesn't need to be encrypted. The encryption is only necessary to generate the result of the script in the Java-code.
Another problem concerns the conversion of arbitrary binary data into strings. This generally leads to corrupted data if the encoding is unsuitable (e.g. ASCII or UTF8). Therefore
...
byte[] encoded = Files.readAllBytes(Paths.get(filePath));
str = new String(encoded, StandardCharsets.US_ASCII); // Doesn't work: ASCII (7-bit) unsuitable for arbitrary bytes, *
...
byte[] AESEncrypted = AESCipher.doFinal(str.getBytes("UTF-8")); // Doesn't work: UTF-8 unsuitable for arbitrary bytes and additionally different from *
String encryptedStr = new String(AESEncrypted); // Doesn't work: UTF-8 unsuitable for arbitrary bytes
...
should be replaced by
...
byte[] encoded = Files.readAllBytes(Paths.get(filePath));
...
byte[] AESEncrypted = AESCipher.doFinal(encoded);
FileOutputStream out = new FileOutputStream("PAYLOAD");
out.write(AESEncrypted);
out.close();
...
A suitable encoding to store arbitrary data in a string is e.g. Base64, but this isn't necessary in this case, because Base64-encoding isn't used in the script either.
Try these changes. If other issues occur, it would be best to test AES encryption, RSA encryption, and key_iv-generation separately. This makes it easier to isolate bugs.
Our C# version of this seemingly simple and standard java RSA encryption code is NOT generating the same result. Help?
NOTE: the java code uses a DER file, so we had to firstly convert it to PEM format (via openssl)
openssl rsa -inform DER -outform PEM -in FOO.der -out FOO.pem
and then that to XML (via BouncyCastle). Specifically:
using (var reader = new StreamReader(PrivateKeyPemFile)) { text =
reader.ReadToEnd(); } string xmlpem = RsaKeyConverter.PemToXml(text);
What are we doing wrong here? (Was it our key conversion? Or is there a CspParameter setting that will magically make this equivalent? Or...)
JAVA code
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
KeySpec keySpec = new PKCS8EncodedKeySpec(keyRawBytes);
java.security.Key key = keyFactory.generatePrivate(keySpec);
javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("RSA");
cipher.init(1, key);
encryptedBytes = cipher.doFinal(data);
C# code
RSACryptoServiceProvider cryptoServiceProvider = new RSACryptoServiceProvider();
cryptoServiceProvider.FromXmlString(pemXmlString);
var encryptedBytes = rsa.Encrypt(data, false);
So I am creating a basic socket program where I want to send an encrypted string in C to a Java program. My C program encrypts the string with a public PEM key. I have converted the matching private PEM key to a DER key and now want to decrypt the string that was sent to my Java program. How do I do this?
At the moment I am getting an IllegalBlockSizeException stating that "Data must not be longer than 256 bytes" when trying to run the code as it stands.
This is what I have at the moment:
C client program...
//Get our public key from the publickey file created by server
FILE *publicKeyFile = fopen("publicKey.pem", "rb");
RSA *rsa = RSA_new();
rsa = PEM_read_RSA_PUBKEY(publicKeyFile, &rsa, NULL, NULL);
if(rsa == NULL) {
printf("Error with public key...\n");
}
else {
//if the public key is correct we will encrypt the message
RSA_public_encrypt(2048, sigMessage, sigMessage, rsa, RSA_PKCS1_PADDING);
}
Java decryption...
public static String decrypt(byte[] encryptedMessage) {
try {
Cipher rsa;
rsa = Cipher.getInstance("RSA");
PrivateKey ourKey = getKey("resources/privateKey.der");
rsa.init(Cipher.DECRYPT_MODE, ourKey);
byte[] utf8 = rsa.doFinal(encryptedMessage);
return new String(utf8, "UTF8");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static PrivateKey getKey(String filePath) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
File f = new File(filePath);
FileInputStream fis = new FileInputStream(f);
DataInputStream dis = new DataInputStream(fis);
byte[] keyBytes = new byte[(int) f.length()];
dis.readFully(keyBytes);
dis.close();
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(spec);
}
You can't encrypt 2,048 bytes with RSA unless you have an RSA key of over 16K. You probably have a 2048 bit RSA key, which limits you to under 256 bytes. Check out the openssl man page for RSA_public_encrypt:
flen must be less than RSA_size(rsa) - 11 for the PKCS #1 v1.5 based
padding modes, less than RSA_size(rsa) - 41 for RSA_PKCS1_OAEP_PADDING
and exactly RSA_size(rsa) for RSA_NO_PADDING. The random number
generator must be seeded prior to calling RSA_public_encrypt().
And RSA_size:
RSA_size() returns the RSA modulus size in bytes. It can be used to
determine how much memory must be allocated for an RSA encrypted
value.
You should not encrypt a full 256 bytes with a 2048 bit key, though. You want to always use random padding with RSA encryption, and choose OAEP over v1.5.
I am developing java application which consumes with the web service, which then validates the user, I have the user enter his username and password. For using this application user required a valid username and password.
I have one context menu which will get activated when there is correct login. Otherwise i want it to get disabled.
And I want only a one time validation. So that, if any other user use that application from same system he dont need to enter the password again.
that means i need to save the password in local system, to use this password throughout the application
Any help regarding saving the password anyhow ?
Well, you can use a public and private key to encrypt or decrypt password.
Edit:
First of all you have to create a public/private key pair. You need the tool openssl for this (http://www.openssl.org/source/ or directly for Windows http://www.openssl.org/related/binaries.html).
Install it, open "cmd" (if you are on windows), navigate to your openssl installation path and enter following lines to generate the keys for server and client:
openssl genrsa -out serverPrivateKey.pem 2048
openssl rsa -in serverPrivateKey.pem -pubout -outform DER -out serverPublicKey.der
openssl genrsa -out clientPrivateKey.pem 2048
openssl pkcs8 -topk8 -nocrypt -in clientPrivateKey.pem -outform der -out clientPrivateKey.der
openssl rsa -in clientPrivateKey.pem -pubout -outform PEM -out clientPublicKey.pem
Now in your web service java application you can import the public key for encryption:
File pubKeyFile = new File("keys/serverPublicKey.der");
byte[] buffer = new byte[(int) pubKeyFile.length()];
DataInputStream in = new DataInputStream(new FileInputStream(pubKeyFile));
in.readFully(buffer);
in.close();
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(new X509EncodedKeySpec(buffer));
and encrypt your password:
String text = password;
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encrypted = cipher.doFinal(text.getBytes());
and save it to your local file system:
FileOutputStream fos = new FileOutputStream("/tmp/encrypted");
fos.write(encrypted);
fos.flush();
fos.close();
The other way for decryption.
Import the private key:
File privKeyFile = new File("keys/clientPrivateKey.der");
byte[] buffer = new byte[(int) privKeyFile.length()];
DataInputStream in = new DataInputStream(new FileInputStream(privKeyFile));
in.readFully(buffer);
in.close();
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(buffer));
read the encrypted file:
File cryptedData = new File("/tmp/encrypted");
buffer = new byte[(int) cryptedData.length()];
in = new DataInputStream(new FileInputStream(cryptedData));
in.readFully(buffer);
in.close();
and decrypt it:
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decrypted = cipher.doFinal(buffer);
String data = new String(decrypted);
System.out.println(data);
You just have to keep your private key secret on the system where your web service is running.
You can provide a web service function which provides the public key to the clients for encryption. Your clients just send the encrypted text string to the web service which decrypts it and authenticate your clients.