I'm trying to implement RSA Encryption in both Java and PHP, but I can't seem to get PHP to recognize my Java public/private keys. Here is the java code to Encode/Decode the Public and Private Keys:
public static byte[] EncodePublicKey(PublicKey _publickey) throws Exception
{
return _publickey.getEncoded();
}
public static PublicKey DecodePublicKey(byte[] _encodedkey) throws Exception
{
KeyFactory fac = KeyFactory.getInstance("RSA");
X509EncodedKeySpec encodedKey = new X509EncodedKeySpec(_encodedkey);
return fac.generatePublic(encodedKey);
}
public static byte[] EncodePrivateKey(PrivateKey _privatekey) throws Exception
{
return _privatekey.getEncoded();
}
public static PrivateKey DecodePrivateKey(byte[] _encodedkey) throws Exception
{
KeyFactory fac = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec encodedKey = new PKCS8EncodedKeySpec(_encodedkey);
return fac.generatePrivate(encodedKey);
}
I first tried using the PEAR Crypt_RSA functions, but it doesn't support X.509 or PKCS8 (it just simply base64 encodes the serialized modulus, exponent and key type). I then tried the OpenSSL "openssl_get_publickey" function but it doesn't appear to recognize the format either.
Any help would be greatly appreciated o.O
You need to convert the binary format (DER) from Java to PEM for OpenSSL (and the PHP bindings). You can test your Java key files using the OpenSSL command line by specifying the -inform DER option on the command line.
<?
function pem2der($pem_data) {
$begin = "KEY-----";
$end = "-----END";
$pem_data = substr($pem_data, strpos($pem_data, $begin)+strlen($begin));
$pem_data = substr($pem_data, 0, strpos($pem_data, $end));
$der = base64_decode($pem_data);
return $der;
}
function der2pem($der_data) {
$pem = chunk_split(base64_encode($der_data), 64, "\n");
$pem = "-----BEGIN PUBLIC KEY-----\n".$pem."-----END PUBLIC KEY-----\n";
return $pem;
}
// load the public key from a DER-encoded file
$pubkey = der2pem(file_get_contents("pubkey"));
?>
For more information about using OpenSSL keys in Java, check out this link.
The PHP functions require PEM encoded keys. It's trivial to convert DER encoded keys into PEM.
Here is my code to convert PKCS#8 private key to PEM,
function pkcs8_to_pem($der) {
static $BEGIN_MARKER = "-----BEGIN PRIVATE KEY-----";
static $END_MARKER = "-----END PRIVATE KEY-----";
$value = base64_encode($der);
$pem = $BEGIN_MARKER . "\n";
$pem .= chunk_split($value, 64, "\n");
$pem .= $END_MARKER . "\n";
return $pem;
}
For public key in X509, replace PRIVATE with PUBLIC in markers.
http://code.google.com/p/simplersalibrary/ is a simple tool, if you want encrypt something in Java and decrypt in PHP or encrypt in java and decrypt in PHP, simplersa can also generate the pem files for PHP.
You can also try to use CastleCrypt, which allows a easy to use RSA Encryption in JAVA AND PHP: https://github.com/wessnerj/CastleCrypt
For the key generation you may want to try it with openssl:
openssl genrsa -out privateKey.pem 2048
openssl pkcs8 -topk8 -nocrypt -in privateKey.pem -outform der -out privateKey.der
openssl rsa -in privateKey.pem -pubout -outform PEM -out publicKey.pem
openssl rsa -in privateKey.pem -pubout -outform DER -out publicKey.der
This commands gives you private and public key in both DER and PEM Format. For JAVA you have to use the .der keys and for PHP the .pem keys.
Related
I am trying to generate an RSA private key via openssl. I use the following commands:
openssl genrsa -out rsaprivkey.pem 1024
openssl rsa -in rsaprivkey.pem -pubout -outform DER -out rsapubkey.dem
openssl pkcs8 -topk8 -outform PEM -in rsaprivkey.pem -inform PEM -out private.pk8
openssl req -new -x509 -key rsaprivkey.pem -out certificato.crt -subj "/C=IT/ST=Italia/L=Roma/O=xxxx/OU=xxxx/CN=xxxx"
i get all necessary files. The problem is that when I try to use the .pk8 file in my code, I get a java.lang.NullPointerException when I try to read the parameters that should be contained in the file (AlgorithmParameters params = epki.getAlgParameters();)
//
// The bytes just read are supposed to be in "EncryptedPrivateKeyInfo" info
// The algorithm will have OID 1.2.840.113549.1.5.3 or be called "PBEWithMD5AndDES",
// (actually, according to RFC2898, that would be "pbeWithMD5AndDES-CBC")
// which means "Password Based Encryption Algorithm, uses Data Encryption Standard in
// Cipher Block Chaining Mode (DES-CBC), uses MD5 to hash a password & salt to get Key
// and Initialization Vector. Defined in RSA's PKCS#5". See RFC2898 for more.
//
EncryptedPrivateKeyInfo epki = new EncryptedPrivateKeyInfo(instream);
System.out.println("Encrypted private key info's algorithm name : " + epki.getAlgName());
AlgorithmParameters params = epki.getAlgParameters();
{
PBEParameterSpec pbeParams = (PBEParameterSpec) (params.getParameterSpec(PBEParameterSpec.class));
Hex hex = new Hex();
String salt = new String(hex.encode(pbeParams.getSalt()), "ASCII");
System.out.println("Encrypted private key info's salt : 0x" + salt);
System.out.println("Encrypted private key info's iteration count: " + pbeParams.getIterationCount());
}
//
// The 'keySpec' is transformed into a 'key' (to be used in a cipher) through a SecretKeyFactory
// The password obtained earlier is used to generate a temporary "keySpec" that is used as
// input to the SecretKeyFactory, then scratched again. What about the PBE algorithm parameter?
// We don't need it here (empirically), but we *must* specify it later on in the cipher.
//
Key encryptedKey = null;
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;
}
When trying to read a RSA private key from a file using the method
public PrivateKey getPrivateKey()
throws NoSuchAlgorithmException,
InvalidKeySpecException, IOException {
final InputStream inputStream = getClass().getClassLoader()
.getResourceAsStream("privatekey");
byte[] privKeyBytes = null;
try {
privKeyBytes = IOUtils.toByteArray(inputStream);
} catch (final IOException exception) {
LOGGER.error("", exception);
IOUtils.closeQuietly(inputStream);
}
LOGGER.debug("privKeyBytes: {}", privKeyBytes);
String BEGIN = "-----BEGIN RSA PRIVATE KEY-----";
String END = "-----END RSA PRIVATE KEY-----";
String str = new String(privKeyBytes);
if (str.contains(BEGIN) && str.contains(END)) {
str = str.substring(BEGIN.length(), str.lastIndexOf(END));
}
KeyFactory fac = KeyFactory.getInstance("RSA");
EncodedKeySpec privKeySpec =
new PKCS8EncodedKeySpec(Base64.decode(str.getBytes()));
return fac.generatePrivate(privKeySpec);
}
I get the exception
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException : algid parse error, not a sequence
at sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(RSAKeyFactory.java:200) ~[na:1.6.0_23]
at java.security.KeyFactory.generatePrivate(KeyFactory.java:342) ~[na:1.6.0_23]
at the fac.generatePrivate(privKeySpec) call.
What does this error mean?
Thanks
Dmitri
I was having this same issue, and the format of the key was NOT the actual problem.
All I had to do to get rid of that exception was to call
java.security.Security.addProvider(
new org.bouncycastle.jce.provider.BouncyCastleProvider()
);
and everything worked
It means your key is not in PKCS#8 format. The easiest thing to do is to use the openssl pkcs8 -topk8 <...other options...> command to convert the key once. Alternatively you can use the PEMReader class of the Bouncycastle lightweight API.
You must make your PCKS8 file from your private key!
private.pem => name of private key file
openssl genrsa -out private.pem 1024
public_key.pem => name of public key file
openssl rsa -in private.pem -pubout -outform PEM -out public_key.pem
private_key.pem => name of private key with PCKS8 format! you can just read this format in java
openssl pkcs8 -topk8 -inform PEM -in private.pem -out private_key.pem -nocrypt
I am trying to generate an encrypted private key and CSR using Java in Matlab. Matlab adds some minor complexity, but this is mostly a Java problem. I start with a private key:
java.security.Security.addProvider(org.bouncycastle.jce.provider.BouncyCastleProvider());
keyGen = java.security.KeyPairGenerator.getInstance('RSA', 'BC');
keyGen.initialize(2048, java.security.SecureRandom());
keypair = keyGen.generateKeyPair();
privateKey = keypair.getPrivate();
If I encrypt the key and output it as PEM:
m=org.bouncycastle.openssl.PKCS8Generator.PBE_SHA1_3DES;
encryptorBuilder = org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8EncryptorBuilder(m);
encryptorBuilder.setRandom(java.security.SecureRandom());
encryptorBuilder.setPasssword(password);
oe = encryptorBuilder.build();
gen = org.bouncycastle.openssl.jcajce.JcaPKCS8Generator(privateKey,oe);
privKeyObj = gen.generate();
fos = java.io.FileWriter('private.pem');
pem = org.bouncycastle.openssl.jcajce.JcaPEMWriter(fos);
pem.writeObject(privKeyObj);
pem.flush();
fos.close();
I get a perfectly good key. The problem is that I want to use the key with jdbc, so I need a DER formatted pk8 key. I cannot figure out how to get this out of BouncyCastle. A kludge workaround that succeeds:
textWriter = java.io.StringWriter();
pem = org.bouncycastle.openssl.jcajce.JcaPEMWriter(textWriter);
pem.writeObject(privateKey);
pem.flush();
thekey = char(textWriter.toString());
cmd = ['echo "' thekey '"|openssl pkcs8 -topk8 -out private.pk8 -inform PEM -outform DER -passout pass:' password];
system(cmd);
Now, obviously this exposes both the unencrypted private key and the password. I've tried all manner of things to coerce privKeyObj to DER, but they typically leave me with:
$openssl pkcs8 -inform DER -outform PEM -in private.pk8 -out private.pem
Error decrypting key
140735211835472:error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag:tasn_dec.c:1201:
140735211835472:error:0D06C03A:asn1 encoding routines:ASN1_D2I_EX_PRIMITIVE:nested asn1 error:tasn_dec.c:765:
140735211835472:error:0D08303A:asn1 encoding routines:ASN1_TEMPLATE_NOEXP_D2I:nested asn1 error:tasn_dec.c:697:Field=version, Type=PKCS8_PRIV_KEY_INFO
The intent of this code is to generate a CSR on the end user's machine which I then sign, and which is encrypted with MAC address of the machine (and a salt), so that the program will only run on the authorized machine, and only authorized machines will be able to access my PostgreSql database.
Suggestions?
I figured it out. In my original code, I had used BcPKCS12PBEOutputEncryptorBuilder. Wrong! The correct call is to JcePKCSPBEOutputEncryptorBuilder. The correct code (in MATLAB, but converting to Java is simple) is:
java.security.Security.addProvider(org.bouncycastle.jce.provider.BouncyCastleProvider());
keyGen = java.security.KeyPairGenerator.getInstance('RSA', 'BC');
keyGen.initialize(2048, java.security.SecureRandom());
keypair = keyGen.generateKeyPair();
privateKey = keypair.getPrivate();
builder=org.bouncycastle.pkcs.jcajce.JcaPKCS8EncryptedPrivateKeyInfoBuilder(privateKey);
m=org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC;
encryptorBuilder = org.bouncycastle.pkcs.jcajce.JcePKCSPBEOutputEncryptorBuilder(m);
password = 'test';
outputBuilder = encryptorBuilder.build(password);
privKeyObj = builder.build(outputBuilder);
fos = java.io.FileOutputStream('testkey.pk8');
fos.write(privKeyObj.getEncoded());
fos.flush();
fos.close();
This generates a DER formatted PCS#8 file.
openssl pkcs8 -inform DER -outform PEM -in testkey.pk8 -out testkey.pem
Now returns the PEM private key. To read the key:
myPath = java.nio.file.Paths.get(pwd,'testkey.pk8');
encodedKey = java.nio.file.Files.readAllBytes(myPath);
privKeyObj =org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo(encodedKey);
cp=org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter();
cp.setProvider('BC');
decryptorBuilder = org.bouncycastle.pkcs.jcajce.JcePKCSPBEInputDecryptorProviderBuilder();
inputBuilder = decryptorBuilder.build(password);
info = privKeyObj.decryptPrivateKeyInfo(inputBuilder);
decodedKey=cp.getPrivateKey(info);
Note that in MATLAB, you don't need to declare the type of the returned object, and you don't need to put "new" in front of a constructor.
Background
RSA key generation with OpenSSL on Linux using the command,
openssl genrsa -out mykey.pem 1024
created the following:
"-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQChs9Fepy5FgeL0gNJ8GHcKRHsYnM2Kkw19zwydDQNyh2hrHWV2
B11wpLFp8d0imcl2Wjb0oV/AxOhb3unQgNzs66LVuXJwS8icp3oIJZtExs6tkxzE
s5mnU68wMeCYtJqHIZOmNblVWvpJMLNAwAVi3oLfnzDDbzjnDapm8M21nQIDAQAB
AoGAZ11P1+acUHgvwMXcRtFIvvp5iYkqZouL00EYOghIjNx75gTbh7A7jbbpZeTi
y6xsuMgAWy4QzGPSeG+tHMhS7+dYQNPuKSv5KtK3V7ubXz/I3ZN1etRVecA56QNw
7HKv6b7srolt08kogGIwpbbfl/mhfJHnv4Jeqd5lNMnK4e0CQQDWFZo4h22OlSaH
ZGd3i4rwLrA0Ux5bkdh7YH0uEeE/nGzpVs1DPhsN8UCyq9LAiKYLlXeeCvwurKwo
OgKlUCkzAkEAwVy2KignoRInFTAaYH8PQRfD835q+oC0Iu21BF68ne06U6wu+wWk
bWiYxTOOb+TGZfA1vA6OAvGVGoXs1bHF7wJBAItGiop0MKYuCl7Sxy1SrxUKir+/
w2Q3QesiHs41+6Byl7hGLEuuv9MWPM0AU5/GRqAKoUNESkPjOi0BcG8z81kCQGGn
OvCreugjzM0skAWv5bpQEExGyixdF5yURFlCpytzBYQAb3Gi9dmze4QMd6EW/wO4
fsrM5vehnlXY0TVTJM0CQQCMPVhub8LSo7T/lCzypvb/cgxJfyITRKcM2asrXud5
r27kbzsXqYum4huHqyFkb3pZammsYA/z89HchylfrD4U
-----END RSA PRIVATE KEY-----"
The following code under Java 6,
KeyPairGenerator keyGen = null;
try {
keyGen = KeyPairGenerator.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
KeyPair pair = keyGen.generateKeyPair();
privateKey = new Base64Encoder().encode(pair.getPrivate().getEncoded());
publicKey = new Base64Encoder().encode(pair.getPublic().getEncoded());`
output the following:
"MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIsJlqFOP+jPyYvrGwh+dff30a3p
uHysMfHYi1MyNSFCsT/2QbOc/k9U/X28WRCMeFwEEnReLULXA9Ywox8GycI/ApMX+DjKBrrLDbpr
ATLiu9+NMK4VSytKFI87P07HAni3RkiO4rFNEINVQ7t38ZmHavuXHjMkLEAK4dyLQO9NAgMBAAEC
gYBN/jv0EmwBUgYSKflJI39TcT263B+0N/fwXXOSYNiy5rF9WstyUP/LSrbEAJLJmLKvk00y391t
4CVz0ma+sdUdAPlS7Nmx9f3BThGOGcDmpjVo1y4e1afWtyu66ba/XDeuf7q5Y/h/pr20/gXl9Gz2
yefQrzU9xXGKZhE/lxJ2IQJBAMELpeAal+Fa+u0InGrowVmV+lge8RZqKRfCDzPPna465E5Qcekb
J0ShsarP5lnUfrNH5g8GLaDGQwYE/UoIpPkCQQC4YRfck5uMlI1K3F9YC3XvmFAJnf9YexoPfNSu
dznOD4rxlwzW/5daPOR0jjlyIRDH/QuUoPIIEn1mt3dnz7X1AkBZciozgl7pPhySA7FmH96mwcUz
W3LdrebIaVRd707iUctDNibxmXFCbaFCwf27laf3LdM9FuHBYtvfSCSMTyERAkEAlNAQsUAVmKZB
T72D2o0Nd/7oAosaD7DzvLJU+idSaWUUEJ+IhnKuFu/0t7oe1WWopLEwypoIHsnFmsTTQ99ajQJA
Scwh3P3RTN4F6Jz1SxRSe6L729xI8xkbco5EsMq5v5BZeoGynqdPUUZdAPcaO2k5UagaSejvzgna
8xIqR7elVQ=="
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCLCZahTj/oz8mL6xsIfnX399Gt6bh8rDHx2ItT
MjUhQrE/9kGznP5PVP19vFkQjHhcBBJ0Xi1C1wPWMKMfBsnCPwKTF/g4yga6yw26awEy4rvfjTCu
FUsrShSPOz9OxwJ4t0ZIjuKxTRCDVUO7d/GZh2r7lx4zJCxACuHci0DvTQIDAQAB"
Questions
How do I put "armor" around the private and public keys created through Java code?
Why is each line of the keys generated through Java code longer than those output by OpenSSL?
Does it make any difference? One of the tools, that other team is using, fails while signing a message using private key generated by Java code mentioned above. However, it works just fine that tool uses the private key generated by OpenSSL.
Is there a way I can export a compatible key with Java?
The OpenSSL private key is in a non-standard format, while the Java code is creating a standard, PKCS-#8–encoded private key.
OpenSSL can convert the standard key format to the non-standard form. You can write Java code to do the same, but it requires some third-party libraries and a good knowledge of ASN.1 helps too.
To convert a PKCS #8 key to OpenSSL format, use OpenSSL's pkcs8 utility.
openssl pkcs8 -nocrypt -inform der < pvt.der > pvt.pem
To convert an RSA key stored as a DER-encoded SubjectPublicKeyInfo to PEM format, use OpenSSL's rsa utility.
openssl rsa -pubin -inform der < pub.der > pub.pem
This assumes that the private key is stored in "binary" (DER) format, not Base-64 encoded. The Java code to create and store keys like this would look something like:
KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
KeyPair pair = gen.generateKeyPair();
FileOutputStream ospvt = new FileOutputStream("pvt.der");
try {
ospvt.write(pair.getPrivate().getEncoded());
ospvt.flush();
} finally {
ospvt.close();
}
FileOutputStream ospub = new FileOutputStream("pub.der");
try {
ospub.write(pair.getPublic().getEncoded());
ospub.flush();
} finally {
ospub.close();
}