I need to convert an RSA private key located in memory in PEM format into a PrivateKey on Android.
The problem seems to have been solved for public keys but I'm struggling to get it to work for a private key. I'm trying the following code:
String pemkey = "MIICWwIBAAKBgQDIuL0SzG1+wgyaoyDyHvYIaG10ePXBHqaKTnYyZfY5RzaEFLE/vBdFN2Di7AMH3/5iN/YFQqLsVjKqzX3E3LM2dJOZ9qSWeYArSyQPbhy0eM/3amwchvtLvhVLm2UqVFLjiPGlyYX3D75ETD5tmgulAc5ZDRtGqYVoLKPmZ0USPwIDAQABAoGAErfDjf65UUJISZ1fw6Rmfic62csz47P3hNtHQ3Dlsra020FQvChOpTpCUzb+G1xkjQU58Iijx9VL+Uiba2HHZmiJX2LgS3KKqKFZKmbKZnZQTiw+2o+4AXhtcAYfSAJE9TgRPEhwhZmzV2cvfUk5AjnOghSn2gGjdD1g4xtH22ECQQD/ZbfEd2HEGqHf6j/AVMW+N/Q1xtYIB8r0CWxF6cNw5iq/8Ce9ujpnAFi0vgtojyKDlgwBp4XMU2C4is49EkFhAkEAyTH96mS8dExAAmi3Mm2seUIEOtKwuLD6BEECecPyZSIOd24tfNbmA7Ri6MpGjyLZoNoJQ0AJGcnWU1tnc8bXnwJAS+jYyP1OwrHDwUDnt+u6ZoJNBJrXzMU8LnKKivEjFPBkbm4b8cljSHAS7Y266FX6xS+Y2/kFzKhPjCo9iGtfoQJAOv39hYyj9TWmTw6FKLQfri49L0I3ru+1Xynwn+NkX2Ls+vfDPqeEKfHqTneA2NdPGGrV7HIKORWFUkuqubfD4QJAK60RuhDSeH+ZljcYLhbHLoTnja/uTcvDAd0M4ll2HUNId4jPbYl1qw7OQwfg8apKmGwp7HGW50o/EItvvJrR7w==";
byte[] encoded = Base64.decode(pemkey, Base64.DEFAULT);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey sessionkey = kf.generatePrivate(keySpec);
(The key is not the same as for my Bitcoin wallet so don't bother ^^)
The last line gives the error
java.security.spec.InvalidKeySpecException:
Must use RSAPublicKeySpec or PKCS8EncodedKeySpec;
was java.security.spec.X509EncodedKeySpec
I tested the pemkey string to be ok in other languages (e.g. Python RSA.importkey) and it works fine.
Edit:
On a suggestion by a comment (and the answer to the question linked as doublicate), I also tried with X509EncodedKeySpec replaced by PKCS8EncodedKeySpec. Then the new error I get is
java.security.spec.InvalidKeySpecException:
java.lang.RuntimeException:
error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag
Thanks to comments by Greggz and James I was able to get it to work. There were two problems:
X509EncodedKeySpec had to be replaced by PKCS8EncodedKeySpec
The provided pemkey was PKCS#1 (to be recognized by 'BEGIN RSA PRIVATE KEY') but needs to be PKCS#8 (to be recognized by 'BEGIN PRIVATE KEY').
Related
today we changed on from the HMAC to the RSA on our login server so I have to change the code in our Andoid app to work with that. Problem is when I try to generate a RSA public key instance from a string, all I get is java.security.spec.InvalidKeySpecException: com.google.android.gms.org.conscrypt.OpenSSLX509CertificateFactory$ParsingException: Error parsing public key exception and I have no idea what am I doing wrong.
Code I use is pretty simple:
val base64KeyString ="LS0tLS.......BASE 64 PUBLIC KEY STRING..........LRVktLS0tLQ=="
val keySpec = X509EncodedKeySpec(base64KeyString.toByteArray())
val keyFactory = KeyFactory.getInstance("RSA")
val pubKey = keyFactory.generatePublic(keySpec) //<= this is where the exception is thrown
Thanks for any help
Java crypto uses actual data on its APIs not base64, so as MichaelFehr commented you need to base64-decode your string; in standard Java (8 up) this can be done with java.util.Base64 but I don't know for Android. But less obviously, your base64 string is wrong -- it is NOT the base64 encoding of a publickey, but rather the encoding of something that begins with 4 bytes with value 2D (which is the character code for hyphen) and ends with 7 bytes which are the character codes for "EY-----" -- in other words, it appears to be the base64 encoding of a slightly broken PEM file (that might possibly encode a publickey) not an actual publickey structure.
As a working example, given the following keypair represented by the (OpenSSL-compatible and widely used) PEM encodings
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAsYU/MoIxYeVuCFcrX+fEH5XX/he/nJTSQ6P4RF38DKLNnNtQ
9XfnaMEPk2EXgSIHk+VXhE9vZNxsV7Emjg6C+eRxUB1NDBtWGX/Ts2cOYD8ZhSum
hOplKKEmdEkzGU/Rem3myfB2BQeN6RSRFigZfsR2I4oaQ5YDWJ3uk6Ix0XN4zQwm
tw1t9W9tgDZATDLW0+8uU02Wc2HrIcyOvDqJDXz/XVY+F+pkrws/ygOhxtVdNY7r
3meSKjSUVkOiXMdaqK+20l/YrvoZRVe6HEITpu9t/x6YDaoTThsbptjkAKBYpSG2
UeXJnnQMyKD2GT3Ouyiupv7lPovvLZZiqfVIWQIDAQABAoIBAGilPKjgmI21+pQO
FrKVZEaeRVIy27BJBl37Rbm/kXo9ammh22qoQRC+Zhkh7gIgdkkXs+x6Cxdw3Kkg
87JgGZOHt8hbTXTqlGm5sakbAh14dOnM3n+R0QoPXSTFlcrBA6JhMOAv0NKMLnqM
ti5Sex0AYQf4PB1FTDmr99ytiyKFK8bvAPiirKB4cEdbMTrxn6rtY324dN4PKdHR
7Sq9JnpYqOoY9adNlhPuN5vq91LCIjoDDkrZ8E8T8o9hUsJXFwsW0GDf4M7s8krj
Dt37zUPh6HSEg/Q1wM+ncEcE3PABeYvIibngfmcwVaMo/0T/9RjQL0EPUOuqxCg+
UlGyFFECgYEA63/vwM+uEil+4Zwqx7dyQ2gbNypEqNnbKelSKj0RpjJvkO7oQ39A
fcr198T1giwNo0cj3oUaduMtPBJUdpUqEYs/p1eNwcGbYSI5csLKy1awHIX5YIDY
oouIq5qipZPYXGFe5bsIZV2kRvzH/e1pW4/eDVgojVD3vKib9nPsi10CgYEAwPlB
KYu3uH5Di2UqNRAryAMCogJzCKHbC0xEMeEhOy0sWvblT1BaqrS941RuAbiaVBn/
nzltKF1WmYHfX2OK6GummYmYkT8QB1TC6ir9etlT54h/w7Hjc3eysGoCLGUr2V/q
kEUou4ut2KiVfUUxyG05Ec3BtyZ+4ZV0fdnAXS0CgYB9j2LHDHOk31b09wygGyDi
65jHGtQsqqr9d5cFSjYkxHNTdO2FP02lRBdMmUjEtLQ4v+9R7umjRZCSnLtH1lPt
sq6njDjae11atqKmm1EAhSG0s4G4gDoAwCCIThQ179PFvWyZU9UwJnM2HgSLUI+B
7/zWZJCKeAb+IW+2QSx3SQKBgEo3UqVc09LD1Mxmov33cy8gYUHXuVAnl6vXsB6S
3An7TKTLcdO4LraafrFQhmJpEgmoWhRtrJqqkyTEuxjfmsvaox9HuQKbg1mecrxG
vmgh71ALwj+MSdLdxT5t9toGKwAWEItd3dWLWdgKtjuQgh34S8uaG5eJ2aB+r2e7
eG0dAoGAX/kgK4WBBH/Paa0g4l5cHD99Lgq02IxCMODG73Vbujxu72spJElQzZGd
eyD3XjHwpa7uL6hzIcXXSpfjLxBCQRu37s7KXbYRWC7YBhV6RlUZlPkrMKr5d+68
vx5dwrelvikoKvhf3sCRbHgD0xdJtUkx0eOD/5kJAbWuh/VWJxE=
-----END RSA PRIVATE KEY-----
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsYU/MoIxYeVuCFcrX+fE
H5XX/he/nJTSQ6P4RF38DKLNnNtQ9XfnaMEPk2EXgSIHk+VXhE9vZNxsV7Emjg6C
+eRxUB1NDBtWGX/Ts2cOYD8ZhSumhOplKKEmdEkzGU/Rem3myfB2BQeN6RSRFigZ
fsR2I4oaQ5YDWJ3uk6Ix0XN4zQwmtw1t9W9tgDZATDLW0+8uU02Wc2HrIcyOvDqJ
DXz/XVY+F+pkrws/ygOhxtVdNY7r3meSKjSUVkOiXMdaqK+20l/YrvoZRVe6HEIT
pu9t/x6YDaoTThsbptjkAKBYpSG2UeXJnnQMyKD2GT3Ouyiupv7lPovvLZZiqfVI
WQIDAQAB
-----END PUBLIC KEY-----
and a token created on jwt.io, the following (Java) code using jjwt verifies and parses the token:
String pubkeyb64 = // note BEGIN/END lines and linebreaks from PEM removed
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsYU/MoIxYeVuCFcrX+fE"+
"H5XX/he/nJTSQ6P4RF38DKLNnNtQ9XfnaMEPk2EXgSIHk+VXhE9vZNxsV7Emjg6C"+
"+eRxUB1NDBtWGX/Ts2cOYD8ZhSumhOplKKEmdEkzGU/Rem3myfB2BQeN6RSRFigZ"+
"fsR2I4oaQ5YDWJ3uk6Ix0XN4zQwmtw1t9W9tgDZATDLW0+8uU02Wc2HrIcyOvDqJ"+
"DXz/XVY+F+pkrws/ygOhxtVdNY7r3meSKjSUVkOiXMdaqK+20l/YrvoZRVe6HEIT"+
"pu9t/x6YDaoTThsbptjkAKBYpSG2UeXJnnQMyKD2GT3Ouyiupv7lPovvLZZiqfVI"+
"WQIDAQAB";
String jws = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.ry5trgLoSgYZt17NOttSkyFhXIKBHjw3OZy8SK_uwukUCMCSh6O5njlRhTA306lXTFrUkb4VCKYk43y_3vTBDzNA7aAZEuwl0pL2O2gfvvcytXnHsnkjaPiigUvNF-pqpvJkOXhTXobKq-76m7ozHTabxbIO0OM4PTkte_2c2922U22qAFuVmfuKRqr3rsMwp24QImppmWsa_rElSqGhc6wIDGwrtdTIFe311Fi8o7L7wWIswRvH4WlsLG6fLGv6yNt-e2Ld5X1kAfozaZqy-hdR9vpq9LqWVWId6VhJ4kayPVnImevctyMY14TlVlwJdRBBgTAdVVl9QHFbMgwozg";
byte[] pubkeyder = Base64.getDecoder().decode(pubkeyb64);
PublicKey pubkey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(pubkeyder));
Claims parsed = Jwts.parserBuilder().setSigningKey(pubkey).build().parseClaimsJws(jws).getBody();
System.out.println (parsed);
Modification to Kotlin, if you need that, should be trivial.
I am new to cryptography and am learning how to use Bouncy Castle in Java for crypto purposes.
I know that Python has Crypto-Charm which I have used
import charm.toolbox.ecgroup
serializedKey = charm.toolbox.ecgroup(prime192v1).deserialize(keyInBytes)
How can I do the same for Java?
Try this:
Deserialize:
KeyFactory keyFactory = KeyFactory.getInstance("EC");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
Serialize:
byte[] keyBytes = privateKey.getEncoded();
Maybe you can need this call (depending on your JDK) previous to KeyFactory.getInstance:
Security.addProvider(new BouncyCastleProvider());
This reference could be useful https://www.bouncycastle.org/fips-java/BCFipsIn100.pdf to understand different examples of code enconding.
In chapter
Password Based Encryption and Key Storage
on this section
Encoding Public and Private Keys
there are some examples to get ideas.
while trying to convert byte[] array to PublicKey I'm getting an error:
Exception in thread "main" java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException: short read of DER octet string
at sun.security.rsa.RSAKeyFactory.engineGeneratePublic(Unknown Source)
at java.security.KeyFactory.generatePublic(Unknown Source)
This is part of code that gives me this error:
byte[] publicKeyBytes = keystring.getBytes();
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
PublicKey publicKey2 = keyFactory.generatePublic(publicKeySpec);
Checked before, byte[] array that i'm getting from string is the same as the original one.
Thanks
According to your comments you convert the bytes of the encoded public key directly to String using the default encoding, which will for sure drop/replace some of the bytes. See the JavaDoc for new String(bytes[]).
So the content of pair.getPublic().getEncoded() and keystring.getBytes() won't be the same.
Use a suitable transport encoding, like Base64. On Java 8, you can use java.util.Base64 for that, on older platforms the Apache Commons Codec's Base64 class can be used.
Is it possible decrypt an encrypted RSA (or others, shouldn't matter) private keys using JCE and/or BouncyCastle provider (not using openssl bundle)?
I can read unencrypted keys just fine using PrivateKeyFactory.
Googling this gets me through examples of using PEMReader (from BC openssl bundle) that has a password applied to it, but - don't want to use openssl bundle, don't necessarily want to use PEM format, and I can decode PEM using PemReader (from provider bundle). It's what can I do with it afterwards is the question.
I'm looking for some mega-function, or a series thereof that can do it, i.e. I am not looking into parsing the ASN1 of the encrypted key, figuring out the encryption method, passing the input through the cipher, etc.
If you have an encrypted PKCS#8 key in binary format (i.e. not in PEM format) the following code shows how to retrieve the private key:
public PrivateKey decryptKey(byte[] pkcs8Data, char[] password) throws Exception {
PBEKeySpec pbeSpec = new PBEKeySpec(password);
EncryptedPrivateKeyInfo pkinfo = new EncryptedPrivateKeyInfo(pkcs8Data);
SecretKeyFactory skf = SecretKeyFactory.getInstance(pkinfo.getAlgName());
Key secret = skf.generateSecret(pbeSpec);
PKCS8EncodedKeySpec keySpec = pkinfo.getKeySpec(secret);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(keySpec);
}
If you have a PEM format, remove the header (first line), the footer(last line) et convert the remaining content from base64 to regular byte array.
I am trying to read the RSA public and private keys files in Java.
My RSA public and private key is generated using PuttyGen. (SSH-2 RSA, 1024 bits)
The code I am using for reading file is:
//public key
pubkeyBytes = getBytesFromFile(new File(pubKeyfileName));
KeySpec pubSpec = new X509EncodedKeySpec(pubkeyBytes);
RSAPublicKey pubKey =(RSAPublicKey) rsakeyFactory.generatePublic(pubSpec);
//private key
privkeyBytes = getBytesFromFile(new File(privKeyfileName));
PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(privkeyBytes);
PrivateKey privKey = rsakeyFactory.generatePrivate(privSpec);
It throws:
java.security.InvalidKeyException: invalid key format
at sun.security.x509.X509Key.decode(Unknown Source)
Putty uses its own key format. You need to export the Putty key to the OpenSSH format - see How to convert SSH keypairs generated using PuttyGen(Windows) into key-pairs used by ssh-agent and KeyChain(Linux).
Then, you need to convert the OpenSSH key to pkcs8 format - see How to Load RSA Private Key From File. The Cygwin version of openssh will work just fine for this; there's no need to find a Unix system to run openssh.