Generating public key instance for JWT validation - java

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.

Related

RSA decryption according to the private key reports an error

My code using RSA on the front end:
const rsa = new JSEncrypt();
rsa.setPublicKey(k);
const resultText = rsa.encrypt("violet");
console.log(resultText);
My code using RSA in the backend:
byte[] inputByte = org.apache.commons.codec.binary.Base64.decodeBase64(str.getBytes("UTF-8"));
byte[] decoded = org.apache.commons.codec.binary.Base64.decodeBase64(privateKey);
PrivateKey priKey = KeyFactory.getInstance("RSA").generatePrivate(new
PKCS8EncodedKeySpec(decoded));
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE,priKey);
String outStr=new String(cipher.doFinal(inputByte));
return outStr;
PublicKey like this:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA13gYdCmOjR9yqQD7ldzG
ZXabSon6SiLceCK6vRXf4NMbF+EQke0vRpqU3IZ/S1pFdvoQswQabsA4zf0WACVT
iaGIhWDlPu3mecri8rYtmOSfd8GCE0vEgFNvSD6IXRLPeLCB+i7WENBa4fCEtW8W
Hzdas96CLiESbjSAruRasQXP2OLqEA2GU83/069vh8uRKzui+yw0aAXZFyFyFRFa
lxYltFadVpz3+kBplvpzuj82t4fc3yCRbrpeRyTyX1sz0ULSxx/k3/p1OuJtIq9Y
9uN0G4gxhcDFJ4L41uXOln5CPapk7tlsYobhhvxYHw1rrweY+06hrQ7r0Hblv2nH
GQIDAQAB
-----END PUBLIC KEY-----
PrivateKey like this:
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEA13gYdCmOjR9yqQD7ldzGZXabSon6SiLceCK6vRXf4NMbF+EQ
ke0vRpqU3IZ/S1pFdvoQswQabsA4zf0WACVTiaGIhWDlPu3mecri8rYtmOSfd8GC
E0vEgFNvSD6IXRLPeLCB+i7WENBa4fCEtW8WHzdas96CLiESbjSAruRasQXP2OLq
EA2GU83/069vh8uRKzui+yw0aAXZFyFyFRFalxYltFadVpz3+kBplvpzuj82t4fc
3yCRbrpeRyTyX1sz0ULSxx/k3/p1OuJtIq9Y9uN0G4gxhcDFJ4L41uXOln5CPapk
7tlsYobhhvxYHw1rrweY+06hrQ7r0Hblv2nHGQIDAQABAoIBAAyqFmXde294BblB
QYhRbafRDNeYvIlW+zZkdC1g98OzJMiGhf7NvhWcSFud3CWFrMeNcyXSe+s+iRgy
Y/SmPP6969RLGa5VNVK7RhOV+aAe7/COAyM3NNmGDehlJIaz8FXbqggWcKaUWIMn
K+WuHdK/4ijoTyZ+8yJfG6Er8tisryLQ9io9+op9g/ZWzaUKgu934/cDxUt70bfm
x+ZEPi6YfkJ1uOpXnnadDyw2RUDcvCM3fK3KF5fqM7SJAXY9b1pmLr+Ccn1qkT9G
I+QHidEsGfJciX5AoHnlIMLPMVIPKBbq4GwC/Ngb41LprNJWlPR38N2ySjky/Jyt
159XWHECgYEA9lx2KfFmyLyVjnkIF3JI50mSZAw4YPBBqB27UInacvxXbjfVURht
xK60GB9OkDbFdeNh89x86Gfwvm5bTq4W8YSH4Obd5Fg8XjTuaicTi03CSfF5SdJn
JLLOUmlqP75gkbEPNUoOfqhqq6IbyJVB3egyL90cd2/wCdJOVLEUly8CgYEA3+Y4
lNdl0xedkDNkWsCyyA4iPSUzVxsuch9hW/VGBwzga8rtiNllpKifVvctQOEu/KUe
vVQRF78ojZaMGT33l6TivnUL54z9Lo9uWghoG8TqMfnG34pFPe3R+zvGP87Hrozw
1EUhiMT198SlB/YHrgGGGlJbG+rlm5GIx3lEdDcCgYA4RSw0LlA0v2ZFkX14pYDj
WxmVwnjKI3ZLqObU4XfE1cA+i4AssrC3wNOfwt7V77ywTYxc/9qD1uHVDS3LzdWt
uoCyrOi3tDOtrNdb5asAIXWkIAR9CRLH/hNEHZHIF3rFLDT2DgE7iso6g59m9DiE
L/nulsleunGQPLnpfDzgvwKBgDRV5Q3tl3CTSZJGYQPRnTikDR7LzkdjJCUq7qAH
IhpNyTuJEKL3ZgnqHGzAlERhHpGRqzDIMMKjPUBzW0YfNPuuYA3y4Bh83UV/42SK
KIOtMK0D3JeuA2dparbWDw4lMIm9iiGkEyWcHH6Q6Z6FxN/InWcTrxZEfu0xRI6T
6wtbAoGAfl5dW9LNoaNfQbgIq+PJKZm9f1frza55mFTJgo3ravdb3GmzWVHN8xRf
nLKyKyNLqbkT35IGc39NkALJLxT5RibkAZLiUiwqdMF63sgODbA9AGTmhe+JHS+V
hBmFnCyp6UiN9E4ZAWcZQILa0rRMftMFngAJ3El0ZP+HziRnNzs=
-----END RSA PRIVATE KEY-----
Bu-t, when i do the java code decryption, it reported such an error:
java.security.InvalidKeyException: IOException : DerInputStream.getLength(): lengthTag=111, too big.
How can i solve this problem ?
1. You are decoding wrong. PEM format has a dash-BEGIN line identifying the type of data, a block of base64 encoding the data, and a dash-END line. The BEGIN and END lines are part of the format, but they do not contain base64-encoded data; only the lines in between contain the base64-encoded data. You are apparently passing the whole thing, including the BEGIN and END lines, to commons.codec.Base64, which results in decoding a bunch of garbage before and after the actual data. That garbage isn't valid ASN.1 DER, so when Java tries to parse it as DER it fails.
2. Plus your data is not a PKCS8-clear privatekey. The PEM type 'RSA PRIVATE KEY' is an OpenSSL-defined format that contains a 'traditional' or 'legacy' format, namely the PKCS1 representation of the private key. This is not PKCS8, which is the only key format Java supports natively; that's why the spec class is named PKCS8EncodedKeySpec, because it is a key spec encoded as PKCS8 and more specifically PKCS8-clear. If you fix the above problem by removing the BEGIN and END lines before base64-decoding, Java can parse the result as DER, but not as a PKCS8-clear key; you get a different exception about 'algid parse error, not a sequence'. To fix this there are 5 approaches:
change whatever process you use to initially generate the keypair so it generates PKCS8, not OpenSSL-legacy PKCS1. Especially since you need anyway to replace the keypair you compromised by publishing it, as 207421 said. You give no clue what that process is or was, so I can't give any details.
convert your generated privatekey, or a copy, to PKCS8-clear. This is not programming or development and offtopic, but if you have or get OpenSSL (on the same or any accessible and secure system), you can do
openssl pkey -in oldfile -out newfile # 1.0.0 up only, but older is now rare
# or
openssl pkcs8 -topk8 -nocrypt -in oldfile -out newfile # even ancient versions
Once you have a PKCS8-clear file, just remove the BEGIN and END lines and base64-decode what is left, and pass that to KeyFactory as PKCS8EncodedKeySpec as you already do.
use https://www.bouncycastle.org . The 'bcpkix' jar has (Java) code to read a large range of OpenSSL-supported PEM formats, including the RSA-PKCS1 private key format you have. There are lots of existing Qs about this; just search for PEMParser and JcaPEMKeyConverter.
convert it yourself. Decode the body of the file you have, after removing the BEGIN and END lines, to get the PKCS1 key, then build the PKCS8 format for that key, and then pass it to KeyFactory as PKCS8EncodedKeySpec. See answers by Noa Resare and Jean-Alexis Aufauvre on Getting RSA private key from PEM BASE64 Encoded private key file or mine in Java: Convert DKIM private key from RSA to DER for JavaMail .
do it entirely yourself. Decode the file you have without BEGIN/END to get PCKS1, parse that as DER following e.g. RFC8447, and build RSAPrivateCrtKeySpec. Some other As on the Q I linked just above do this. However, this requires either: using undocumented internal sun.* classes, which used to work in Java (hence the existing answers) but which 'modular' Java versions (9 up) since 2017 have steadily made more difficult or impossible; using BouncyCastle which has documented (and good) support for ASN.1 -- but then it's easier to use bcpkix for the whole job as above; or writing your own ASN.1 parsing, which is a good deal of work.
PS: encrypting text with RSA is usually a bad design; it's not suited for that. But that's not really a programming issue and doesn't belong here.

php: string encryption using RSA private key (convert from JAVA to php)

I have this JAVA code & I need to write the same thing in php:
public static String signMsg(String msg, String privateKey)
throws Exception {
byte[] bytes = Base64.getDecoder().decode(privateKey);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
Signature ps = Signature.getInstance("SHA256withRSA");
ps.initSign(kf.generatePrivate(spec));
ps.update(msg.getBytes("UTF-8"));
byte[] sigBytes = ps.sign();
return Base64.getEncoder().encodeToString(sigBytes);
}
Any idea how to do that?
Thanks in advance :)
Regarding your first approach: A signature is created with the private key. The public key is used to verify the signature. Regarding your second approach: A HMAC is not the same as a signature.
The Java code loads a private key in PKCS8 format, PEM encoded without header and footer. In the PHP code the key can be read in the same format and encoding. Alternatively, the key can be loaded in PKCS#1 format. Regarding the encoding, a PEM or DER encoded key is also accepted.
Additionally, it must be specified which algorithm is used for signing. The Java code applies RSA with PKCS#1 v1.5 padding and SHA-256 as digest. Furthermore, the generated signature is Base64 encoded. In order for the PHP code to provide the same RSA signature, the same parameters must be used.
Note that signing does not necessarily generate the same signature using the same message and the same key. It depends on the algorithm. However, in the case of RSA with PKCS#1 v1.5 padding, always the same signature is generated (deterministic). For PSS, on the other hand, a different signature is generated each time (probabilistic).
The following PHP code uses the PHPSECLIB and generates the same signature as the Java code:
use phpseclib3\Crypt\RSA;
$privateKey= 'MIIEvg...';
$signatureB64 = base64_encode( // Base64 encode signature
RSA::load($privateKey)-> // Choose RSA, load private PKCS8 key
withHash('sha256')-> // Choose SHA-256 as digest
withPadding(RSA::SIGNATURE_PKCS1)-> // Choose PKCS#1 v1.5 padding
sign('The quick brown fox jumps over the lazy dog') // Sign messsage
);
print($signatureB64);

Generate openssl keypair using java

I need to generate openssl keypair in java which simulate the below:
openssl ecparam -name prime256v1 -genkey -noout -out prime256v1.key
openssl pkcs8 -topk8 -in prime256v1.key -out prime256v1-priv.pem -nocrypt
openssl ec -in prime256v1-priv.pem -pubout -out prime256v1-pub.pem
My java program is as below:
public static void main(String args[]) throws Exception{
Security.addProvider(new BouncyCastleProvider());
KeyPairGenerator g = KeyPairGenerator.getInstance("ECDSA", "BC");
ECGenParameterSpec spec = new ECGenParameterSpec("secp256r1");
g.initialize(spec);
KeyPair keyPair = g.generateKeyPair();
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
String publicKeyContent = Base64.encode(publicKeyBytes);
String publicKeyFormatted = "-----BEGIN PUBLIC KEY-----" + System.lineSeparator();
for (final String row:
Splitter
.fixedLength(64)
.split(publicKeyContent)
)
{
publicKeyFormatted += row + System.lineSeparator();
}
publicKeyFormatted += "-----END PUBLIC KEY-----";
BufferedWriter writer = new BufferedWriter(new FileWriter("publickey.pem"));
writer.write(publicKeyFormatted);
writer.close();
byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
String privateKeyContent = Base64.encode(privateKeyBytes);
String privateKeyFormatted = "-----BEGIN PRIVATE KEY-----" + System.lineSeparator();
for (final String row:
Splitter
.fixedLength(64)
.split(privateKeyContent)
)
{
privateKeyFormatted += row + System.lineSeparator();
}
privateKeyFormatted += "-----END PRIVATE KEY-----";
BufferedWriter writer2 = new BufferedWriter(new FileWriter("privatekey.pem"));
writer2.write(privateKeyFormatted);
writer2.close();
}
The above code works but the private key generated seem to be longer than the one generated via the command line utility I have mentioned at the top.
Privatekey with command line:
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgGuyf3+/6+rnDKw0D
WbxVyggwNL0jlTVAzGm6cpl3ji2hRANCAAQ7zLtxLLvl6LJHJAlYAZr4hAc09fZn
bAniYIeKVqVBdKIvb5R445PFiUDFcfyneeX/resPXJHMEm/vAxfQeMqL
-----END PRIVATE KEY-----
Privatekey with java:
-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgYFPrkmxnwjVBgpUV
B02/luLD1rt9
UWZHj62YdhwYQESgCgYIKoZIzj0DAQehRANCAATZp7Jl8KXXApA
hvv9qeQtX5LbHQkrCdx3DfkUC
GgCUMSJWKxs7yJPNKtFZnFUTFZfyEF76fdEzky
zIon5H04MX
-----END PRIVATE KEY-----
Even if I remove the 2 extra lines here, even then, this seems to be a bigger key.
Publickey with Command line:
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEO8y7cSy75eiyRyQJWAGa+IQHNPX2
Z2wJ4mCHilalQXSiL2+UeOOTxYlAxXH8p3nl/63rD1yRzBJv7wMX0HjKiw==
-----END PUBLIC KEY-----
Public key with Java:
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2aeyZfCl1wKQIb7/ankLV+S2x0JK
wncdw35FAhoA
lDEiVisbO8iTzSrRWZxVExWX8hBe+n3RM5MsyKJ+R9ODFw==
-----END PUBLIC KEY-----
So, my first question is about the private key length. It seems longer.
My second question is it seems I am not splitting the generated key bytes properly. There are certainly more lines than expected. How to correct it?
... the private key length ... seems longer
It is, or to be precise the structure representing/containing the private key is longer. Java includes the optional -- and unnecessary (redundant) in a PKCS8 -- 'parameters' field of the algorithm-specific data, which is ECPrivateKey defined in SEC1 appendix C.4, while OpenSSL does not. This will be ignored when read back in. The actual key value within both structures is the correct size.
I am not splitting the generated key bytes properly
Rather, splitting the (base64) characters that encode the bytes (of the key structure).
Look at the output from your Base64.encode before the Splitter. I bet you will find it already contains newlines after each 76 base64 characters, consistent with the MIME standards (RFC 1521 et seq) which some people think are more common (or more important?) or at least newer than PEM. (Although XML and JWX are even newer and now quite common, and don't insert linebreaks at all.) As a result your Splitter takes:
the first 64 chars from the first line
the remaining 12 chars from the first line, a newline, and 51 chars from the second line
the remaining 25 chars from the second line, a newline, and (up to) 38 chars from the third line
etc.
Although OpenSSL writes PEM files with body linebreaks every 64 chars (except the last line), per the PEM standard (RFC 1421), it has always been able to read files with any multiple of 4 up to 76 chars, consistent with MIME. Recent versions since 1.1.0 in 2016, now fairly widely adopted, can read lines up to several hundred chars. Thus if your files are to be read by (anything using) the OpenSSL library, you could just write the split-at-76 version without any further change except ensuring there is a linebreak terminating the last line. Other software may vary; if you need to be safe, or strictly compliant, first remove the linebreaks from your Base64.encode output and then add them back at the correct spacing of 64. See the recently published respecification.
PS: if you use Java to put this key in a PKCS12 keystore (which requires you have/get/create a certificate for it), openssl commandline can read that directly, and convert (1) the privatekey to PEM, (2) the certificate to PEM from which you can extract the publickey in PEM.

How can I read a rsa public key form string(Generate by java and I want to read it with Python)

The code generate the public key is
public static final String CHARSET = "UTF-8";
public static final String RSA_ALGORITHM = "RSA";
public static Map<String, String> createKeys(int keySize){
KeyPairGenerator kpg;
try{
kpg = KeyPairGenerator.getInstance(RSA_ALGORITHM);
}catch(NoSuchAlgorithmException e){
throw new IllegalArgumentException("No such algorithm-->[" + RSA_ALGORITHM + "]");
}
kpg.initialize(keySize);
KeyPair keyPair = kpg.generateKeyPair();
Key publicKey = keyPair.getPublic();
String publicKeyStr = Base64.encodeBase64URLSafeString(publicKey.getEncoded());
Key privateKey = keyPair.getPrivate();
String privateKeyStr = Base64.encodeBase64URLSafeString(privateKey.getEncoded());
Map<String, String> keyPairMap = new HashMap<String, String>();
keyPairMap.put("publicKey", publicKeyStr);
keyPairMap.put("privateKey", privateKeyStr);
return keyPairMap;
}
public static RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
return key;
}
The key is then send to me, and I want to read it with python importKey(). But I always get the error "RSA key format is not supported".
The key is "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDGrLFBqubzi45M_yxs5Ps4XW3DIOeAo5x7Ca9EYmWAig3Rb3Efm2PCgipwNube2Ae5eUI5dYlQW32FSF81rw7vNdwfODDzITyWRPLEuVbBbkF5zD6kTxycqlVbH-uTyb95181jpY_XY6tmEOCZCq3mZhil9VA4ZvAoSBcJ8muXaQIDAQAB"
After searching with Google, I've try to add header "-----BEGIN RSA PUBLIC KEY-----" to it and nothing different.
You are using a 'URLsafe' base64 encoder, although not the standard Java one. 'URLsafe' uses different characters (for code values 62 and 63) and no padding, and no linebreaks. PEM format (which was designed long before URLsafe encoding existed, indeed over a year before URLs existed!) uses the traditional base64 characters (now associated mostly with MIME), with padding, and with linebreaks (every 64 characters). Although not all software checks for linebreaks; you don't say which Python crypto library you are using (there are several) so I can't check if it cares about this point.
In Java 8 up you can use Base64.getMimeEncoder() to handle most of these, but if you are stuck on older Java (see below) and/or some other library, you'll have to give details about it. You could convert the - _ characters to + /, add = padding to a multiple of 4, and add linebreaks if and as needed.
OTOH the Python libs I've looked at accept 'DER' (i.e. binary) as well as PEM, so you could just decode the base64 (many decoders can handle lack of padding and at least some can handle both charsets with or without specification) and use that as-is.
The publickey encoding used by Java (which it imprecisely calls "X.509") and also by OpenSSL and some other things is generic and includes an algorithm identifier, so the correct PEM labels are -----BEGIN PUBLIC KEY----- and ------END PUBLIC KEY----- (NO RSA).
You don't say what Java you use, but it apparently defaulted to 1024-bit for RSA, which is obsolete for several years and is no longer considered to provide adequate safety margin (although there are no open reports yet of actually breaking it). 2048 is now widely considered the minimum, and some applications or environments for various reasons use more. But deciding what crypto parameters to use (and indeed whether your application should even be using RSA, and if so which variant) are not programming questions and offtopic for SO; they belong on either crypto.SX (for the underlying principles) or security.SX (for applications).

How to decrypt a private key in Java (without BC openssl)

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.

Categories