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.
Related
I had generate public key using Java Spring Security, but I can not use that public key to encrypt the data using Nodejs crypto library. I think it is because of its format(X509).
My Nodejs code
module.exports.encryptRsa = (toEncrypt, pemPath) => {
let absolutePath = path.resolve(pemPath);
let publicKey = fs.readFileSync(absolutePath, "utf8");
let buffer = Buffer.from(toEncrypt);
let encrypted = crypto.publicEncrypt(publicKey, buffer);
return encrypted.toString("base64");
};
My Java code
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(keyAlgorithm);
keyGen.initialize(2048);
KeyPair keyPair = keyGen.genKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
byte[] privateKeyBytes = privateKey.getEncoded();
byte[] publicKeyBytes = publicKey.getEncoded();
String formatPrivate = privateKey.getFormat(); // PKCS#8
String formatPublic = publicKey.getFormat(); // X.509
FileWriter fos = new FileWriter("publicKey.pem");
fos.write("-----BEGIN RSA PUBLIC KEY-----\n");
fos.write(enc.encodeToString(publicKeyBytes));
fos.write("\n-----END RSA PUBLIC KEY-----\n");
fos.close();
Java's getEncoded() method returns the public key in format called 'spki' by Node crypto. Java's name for that format is "X.509", an unfortunate choice because it causes confusion with certificates of that name.
The proper PEM header for spki keys is simply -----BEGIN PUBLIC KEY-----. Just get rid of RSA in the header and footer.
I have a private key file in the .der format. I'm trying to save this private key as a PrivateKey object (with Java) like this:
PrivateKey clientPrivKey = getPrivateKeyFromKeyFile("C:\\Users\\Bob\\Desktop\\Assignments\\Project\\VPN Project\\src\\client-private.der");
This is what the getPrivateKeyFromKeyFile method looks like:
private static PrivateKey getPrivateKeyFromKeyFile(String keyfile) throws Exception
{
Path path = Paths.get(keyfile);
byte[] privKeyByteArray = Files.readAllBytes(path);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privKeyByteArray);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey myPrivKey = keyFactory.generatePrivate(keySpec);
return myPrivKey;
}
But when I try this, I keep getting InvalidKeySpecException because of this line of code:
PrivateKey myPrivKey = keyFactory.generatePrivate(keySpec);
I'm not sure what's the issue here. I opened up the private key file and everything looks fine. It starts with -----BEGIN RSA PRIVATE KEY----- and ends with -----END RSA PRIVATE KEY-----.
And in case it's relevant, I created this private key using this OpenSSL command:
genrsa -out client-private.der 2048
A file generated with
openssl genrsa -out <path to output-file> 2048
is actually not a .der-file, but a .pem-file (see e.g. What are the differences between .pem, .cer and .der?) and the data are not stored in the PKCS8-format, but in the PKCS1-format (see e.g. PKCS#1 and PKCS#8 format for RSA private key).
Keys in the PKCS1-format can not be processed directly using standard Java tools. For this, third-party libraries like BouncyCastle are necessary (see e.g. Read RSA private key of format PKCS1 in JAVA).
Another possibility is to convert the PKCS1-formatted key into a PKCS8-formatted key with OpenSSL first (see e.g. Load a RSA private key in Java (algid parse error, not a sequence)):
openssl pkcs8 -topk8 -inform PEM -outform PEM -in <path to the input-pkcs1-pem-file> -out <path to the output-pkcs8-pem-file> -nocrypt
And then, after (programmatic) deletion of the Beginn-/End-line and after base64-decoding the private key can be generated (see e.g. How to read .pem file to get private and public key) e.g. with
private static PrivateKey getPrivateKeyFromKeyFile(String keyfile) throws Exception
{
Path path = Paths.get(keyfile);
byte[] privKeyByteArray = Files.readAllBytes(path);
// added ----------------------------------------------------------------
String privKeyString = new String(privKeyByteArray);
privKeyString = privKeyString.replace("-----BEGIN PRIVATE KEY-----", "");
privKeyString = privKeyString.replace("-----END PRIVATE KEY-----", "");
privKeyString = privKeyString.replace("\r\n", "");
privKeyByteArray = Base64.getDecoder().decode(privKeyString);
// ----------------------------------------------------------------------
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privKeyByteArray);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey myPrivKey = keyFactory.generatePrivate(keySpec);
return myPrivKey;
}
I have an unencrypted PKCS8 encoded file that represents a Private Key. It can be any of these private key types - RSA, DSA or EC. I viewed these files in an ASN1 decoder (https://lapo.it/asn1js/) and I could see the type (RSA, DSA or EC) in the data.
Is there a way to read the PKC8 private key data into the correct Private Key Java object without specifying the key type in code like this -
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(pkcs8key);
KeyFactory factory = KeyFactory.getInstance("RSA"); // Avoid "RSA" here?
PrivateKey privateKey = factory.generatePrivate(spec);
Is there a way to avoid specifying the algorithm in KeyFactory.getInstance("RSA")? Shouldn't this be determined from the PKCS8EncodedKeySpec since it is available in the PKCS8 data?
Sample unencrypted PKCS8 data and their ASN1 decodings which show the key type -
DSA - link
EC - link
RSA - link
This can be achieved with the help of BouncyCastle APIs -
/** Read a PKCS#8 format private key. */
private static PrivateKey readPrivateKey(InputStream input)
throws IOException, GeneralSecurityException {
try {
byte[] buffer = new byte[4096];
int size = input.read(buffer);
byte[] bytes = Arrays.copyOf(buffer, size);
/* Check to see if this is in an EncryptedPrivateKeyInfo structure. */
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
/*
* Now it's in a PKCS#8 PrivateKeyInfo structure. Read its Algorithm
* OID and use that to construct a KeyFactory.
*/
ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(spec.getEncoded()));
PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject());
String algOid = pki.getPrivateKeyAlgorithm().getAlgorithm().getId();
return KeyFactory.getInstance(algOid).generatePrivate(spec);
} finally {
input.close();
}
}
I've seen a number of similar questions, but nothing has quite worked for me. I am simply trying to convert an RSA public key that's in PEM format that I've retrieved from a server into a PublicKeyin Android. Can anyone point me in the right direction?
EDIT:
I've successfully used the following code to convert the PEM into a PublicKey, but upon encoding a message, I get unexpected output...
public PublicKey getFromString(String keystr) throws Exception
{
// Remove the first and last lines
String pubKeyPEM = keystr.replace("-----BEGIN PUBLIC KEY-----\n", "");
pubKeyPEM = pubKeyPEM.replace("-----END PUBLIC KEY-----", "");
// Base64 decode the data
byte [] encoded = Base64.decode(pubKeyPEM);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey pubkey = kf.generatePublic(keySpec);
return pubkey;
}
public String RSAEncrypt(final String plain) throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException, BadPaddingException, IOException {
if (pubKey!=null) {
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
encryptedBytes = cipher.doFinal(plain.getBytes());
Log.d("BYTES", new String(encryptedBytes));
return Hex.encodeHexString(encryptedBytes);
}
else
return null;
}
The output looks like this:
b6813f8791d67c0fa82890d005c8ff554b57143b752b34784ad271ec01bfaa9a6a31e7ae08444baef1585a6f78f3f848eecb1706bf7b2868fccefc9d728c30480f3aabc9ac5c3a9b4b3c74c2f7d6f0da235234953ea24b644112e04a2ec619f6bf95306ef30563c4608ec4b53ed7c15736d5f79c7fa1e35f2444beb366ae4c71
when I expect something closer to:
JfoSJGo1qELUbpzH8d4QXtafup+J2F9wLxHCop00BQ4YS0cRdRCKDfHpFPZQYjNeyQj00HwHbz+vj8haTPbpdqT94AHAl+VZ+TPAiUw1U5EXLLyy4tzbmfVI7CwvMm26lwB4REzYUZdedha1caxMEfxQ5duB+x4ol9eRZM/savg=
Is there some formatting or file type that I'm missing?
To answer my own question...The first output is in hex and the second output is in base 64. Just change the return statement to return new String(Base64.encode(encryptedBytes));
and you'll be good!
This doesn't answer the question, but I find the content relevant. Posting as an answer because it doesn't fit as a comment.
PEM vs DER
PEM basically encapsulates a DER-encoded certificate or key.
DER is binary, PEM is text; so PEM can easily be copy-pasted to an email, for example.
What PEM does is:
Encode the DER certificate or key using Base64, and
Delimit the result with -----BEGIN <something>----- and -----END <something>-----.
The key or certificate is the same, just represented in a different format.
Mostly paraphrasing from ASN.1(wiki).
DER to Android/Java public key
The following is an example of how to use a key factory in order to
instantiate a DSA public key from its encoding. Assume Alice has
received a digital signature from Bob. Bob also sent her his public
key (in encoded format) to verify his signature. Alice then performs
the following actions:
X509EncodedKeySpec bobPubKeySpec = new X509EncodedKeySpec(bobEncodedPubKey);
KeyFactory keyFactory = KeyFactory.getInstance("DSA");
PublicKey bobPubKey = keyFactory.generatePublic(bobPubKeySpec);
...
Note that bobEncodedPubKey is DER-encoded in this sample.
https://developer.android.com/reference/java/security/KeyFactory
PEM to Android/Java public key
Similar to what is done for DER, but do the following beforehand:
Remove the BEGIN/END delimitation, and
Decode the content in Base64 to obtain the original DER.
(The question already shows code on how to do this.)
I'm trying to encrypt some binary data in Java with a public key as described on this useful page:
http://www.junkheap.net/content/public_key_encryption_java
As directed by the page, I created public and private keys using the commands:
openssl genrsa -aes256 -out private.pem 2048
openssl rsa -in private.pem -pubout -outform DER -out public.der
Now I save encrypt some data with a small program:
public class Rsa {
public static void main(String[] args) throws Exception, IOException {
File keyFile = new File("public.der");
byte[] encodedKey = new byte[(int) keyFile.length()];
new FileInputStream(keyFile).read(encodedKey);
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encodedKey);
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey pk = kf.generatePublic(publicKeySpec);
Cipher rsa = Cipher.getInstance("RSA");
rsa.init(Cipher.ENCRYPT_MODE, pk);
FileOutputStream fileOutputStream = new FileOutputStream(
"encrypted.rsa");
OutputStream os = new CipherOutputStream(fileOutputStream, rsa);
byte[] raw = new byte[245];
raw[0] = 4;
os.write(raw);
os.flush();
os.close();
}
}
The above code works, but when I change the size of the byte array to 246, it produces a zero-length file!
What am I doing wrong?
CipherOutputStream tends to swallow exceptions generated by the Cipher and OutputStream objects it wraps. The Sun RSA implementation will not encrypt more than than M-11 bytes, where M is the length in bytes of the modulus. This is true for the default PKCS1Padding, which is what you should always use unless you really know what you are doing. You can specify NoPadding and thereby get the full M bytes.
RSA is not the correct choice for encrypting bulk data. The generally accepted method for encrypting data with RSA is to generate a random symmetric session key K. e.g. an AES key, encrypt the data with the symmetric algorithm with K, then encrypt K using the RSA keys of all the receipients.