This issue is generated in continuation of past question How to RSA verify a signature in java that was generated in php . That code work for simple text. But Now I have requirement for signing and verifying the text which also have a public key ( other than verification key ) in format.
text1:text2:exported-public-key
Example :
53965C38-E950-231A-8417-074BD95744A4:22-434-565-54544:MIIBCgKCAQEAxWg6ErfkN3xu8rk9WsdzjL5GpjAucMmOAQNeZcgMBxN+VmU43EnvsDLSxUZD1e/cvfP2t2/dzhtV6N2IvT7hveuo/zm3+bUK6AnAfo6pM1Ho0z4WetoYOrHdOVNMMPaytXiVkNlXyeWRF6rl9JOe94mMYWRJzygntiD44+MXsB6agsvQmB1l8thg/8+QHNOBBU1yC4pLQwwO2cb1+oIl0svESkGpzHk8xJUl5jL6dDnhqp8+01KE7AGHwvufrsw9TfVSAPH73lwo3mBMVXE4sfXBzC0/YwZ/8pz13ToYiN88DoqzcfD3+dtrjmpoMpymAA5FBc5c6xhPRcrn24KaiwIDAQAB
PHP Code :
$rsa = new Crypt_RSA();
$keysize=2048;
$pubformat = "CRYPT_RSA_PUBLIC_FORMAT_PKCS1";
$privformat = "CRYPT_RSA_PRIVATE_FORMAT_PKCS8";
$rsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS8);
$rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);
$d = $rsa->createKey($keysize);
$Kp = $d['publickey'];
$Ks = $d['privatekey'];
$rsa = new Crypt_RSA();
$rsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS8);
$rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);
$d = $rsa->createKey($keysize);
$Kver = $d['publickey'];
$KSign = $d['privatekey'];
$plainText = "53965C38-E950-231A-8417-074BD95744A4:22-434-565-54544:".$Kp;
// Signing
$hash = new Crypt_Hash('sha256');
$rsa = new Crypt_RSA();
$rsa->loadKey($KSign);
$rsa->setSignatureMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$rsa->setHash('sha256');
$signature = $rsa->sign($plainText);
$signedHS = base64_encode($signature);
// Verification
$signature = base64_decode($signedHS);
$rsa->loadKey($Kver);
$status = $rsa->verify($plainText, $signature);
var_dump($status);
JAVA Code
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.math.BigInteger;
import java.security.spec.X509EncodedKeySpec;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
//import java.util.Base64;
//import java.util.Base64.Decoder;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class VerifySig {
public static RSAPublicKey fromPKCS1Encoding(byte[] pkcs1EncodedPublicKey) {
// --- parse public key ---
org.bouncycastle.asn1.pkcs.RSAPublicKey pkcs1PublicKey;
try {
pkcs1PublicKey = org.bouncycastle.asn1.pkcs.RSAPublicKey
.getInstance(pkcs1EncodedPublicKey);
} catch (Exception e) {
throw new IllegalArgumentException(
"Could not parse BER PKCS#1 public key structure", e);
}
// --- convert to JCE RSAPublicKey
RSAPublicKeySpec spec = new RSAPublicKeySpec(
pkcs1PublicKey.getModulus(), pkcs1PublicKey.getPublicExponent());
KeyFactory rsaKeyFact;
try {
rsaKeyFact = KeyFactory.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("RSA KeyFactory should be available", e);
}
try {
return (RSAPublicKey) rsaKeyFact.generatePublic(spec);
} catch (InvalidKeySpecException e) {
throw new IllegalArgumentException(
"Invalid RSA public key, modulus and/or exponent invalid", e);
}
}
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
String pkey = "MIIBCgKCAQEA+8fKYCT4QiFUdsJ7VdF4xCkVmq/Kwc/10Jl3ie6mvn8hEsC3NAtMJu+Od12gyWYsS0zBDiQ8h2pGZ7p4uWqenc01dRRrq+g968zmoCKPUllPUuR6v9o+wYTX/os4hgaQSBg7DQn4g3BEekcvyk6e6zAMvuhHjeqnrinhCMFgJUhFL8zFNoyaH559C0TNbR6BTKzOoikah8cKhu4UOga0tWDC0I2Ifus/sHOwVaOBkDFIzD6jBxDH/QF8FsrLLTocuIb7Y6lVxFPPtgiUJku6b7wKExV0bPJvm6/Xhv1GX1FpMrA0Ylzj5IFviuviwgo534EcZQ/Hx3aIf4oPG8jVTQIDAQAB";
byte[] dpkey = Base64.decodeBase64(pkey);
RSAPublicKey publicKey = fromPKCS1Encoding(dpkey);
String plainData = "53965C38-E950-231A-8417-074BD95744A4:22-434-565-54544:MIIBCgKCAQEArszIunGg3ievJOpgesYQsp3nPGgrW+3VwkivkkktOXUBRzb3G3mZzidEjG6LxNe/rrNe0UczmnSHQoSBxJCHyUnCWNfScBD66CFG4hLo5Z1gxrP8D2M2lCa6ap2PWcsKiWqlu38EinMeBjBvB4aYpF7+FkFy64ObxR4pfVZxnxradkD0HvvMPLMbyeHxeGqYf8orERf9jfuKTdY8V44rxht2D2fg2WhB1+XL0JulsPvgOaSK3RPnwi+RQAJbihCIh5Zznn0KQCs5pIWoT3XKe1DMpQuEmphSOY9ZUg3AwlOrpRV+565x6GCSc615/6nowmqKzE4T7qT5nbH+ctiEHQIDAQAB";
String data = "iD96rNeR51BF2TUZSaw+QhW8SnsMXE5AdJiDVmJk6LL55jC26PBCnqXrFo2lsQt8aWRsZc0bHFGCcuIbhHA+Duo1/PwrxTqC5BZFL/frqsRSVa+vpvGEnj3xe4iImTEasMicQzzaAG9IWIgkRZ272lUZ8PqdtTuqAsRIwir6fEsfVs5uIErEWM18R4JxlFBc3LDIjFOFemEPSVIEBHwWht1c/CrdTtxPRIiugEb1jdofEBUNcWPZgfvApVx5+0aS9WTl31AY+RMlvp+13P/FQgAMnH9rvBdopRIVsZUNlMf8AOE2afhLPfOgx+41rzCB2wGCrRGELbml466WJ3wYNQ==";
byte[] ciphertext = Base64.decodeBase64(data);
System.out.println(new String(plainData.getBytes(), UTF_8));
verifyBC(publicKey, plainData, ciphertext);
System.out.flush();
}
private static void verifyBC(PublicKey publicKey, String plainData,
byte[] ciphertext) throws Exception {
// what should work (for PKCS#1 v1.5 signatures), requires Bouncy Castle provider
//Signature sig = Signature.getInstance( "SHA256withRSAandMGF1");
Signature sig = Signature.getInstance( "SHA256withRSA");
sig.initVerify(publicKey);
sig.update(plainData.getBytes(UTF_8));
System.out.println(sig.verify(ciphertext));
}
}
It not gave any error but just return false when using public key in plainText. If try after removing with public key, It works and return true.
PHP is working fine and signature is verified in all cases.
I suspecting if java is unable to verify data having base 64 text/public key as text ?
UPDATE : I compare binary bytes of both two times and result show minor difference.
First Case
PHP -> ��#C:���sQ
JAVA -> ��/#C:���sQ
Second Case
PHP -> ��]Q0l�O+
JAVA -> ��]Q0l�
If php base64 is not compatible with apache base 64 ?
When I run the PHP code, I'm noticing that the $Kp variable contains a key in the wrong format:
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAqCJ/2E+YZvXJyabQmi0zZlaXXGbfXHt8KYS27i+PAJKBODmevTrS
w59S5AOy2l7lB4z5mYHuwdT6bm6YYXgE0gnoX/b2L65xdD9XtlenS4Zm15TVTdR5
zde4nBa0QPKfhFvthOmdPr9xDhDb8Rojy/phX+Ftva33ceTXoB+CtLyidMWbQmUh
ZufnI7MwIOPAIzXNJJ85eyUjBdoNMwlAPZo9vYQWeiwYGyP1fjQwEWZgjCH/LJjl
sNR1X9vp5oi8/4omdnFRvKLpkd5R7WMmMfAyAXe7tcfMSXuVAgMWEj9ZG0ELpXbG
S3CK6nvOp2gFF+AjHo9bCrh397jYotE3HQIDAQAB
-----END RSA PUBLIC KEY-----
When I strip out all the extra formatting and export the key as a single line of base64, it works.
PHP code:
function extract_key($pkcs1) {
# strip out -----BEGIN/END RSA PUBLIC KEY-----, line endings, etc
$temp = preg_replace('#.*?^-+[^-]+-+#ms', '', $pkcs1, 1);
$temp = preg_replace('#-+[^-]+-+#', '', $temp);
return str_replace(array("\r", "\n", ' '), '', $temp);
}
$rsa = new Crypt_RSA();
$keysize=2048;
$pubformat = "CRYPT_RSA_PUBLIC_FORMAT_PKCS1";
$privformat = "CRYPT_RSA_PRIVATE_FORMAT_PKCS8";
$rsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS8);
$rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);
$d = $rsa->createKey($keysize);
$Kp = $d['publickey'];
$Ks = $d['privatekey'];
$rsa = new Crypt_RSA();
$rsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS8);
$rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);
$d = $rsa->createKey($keysize);
$Kver = $d['publickey'];
$KSign = $d['privatekey'];
file_put_contents("pub_verify_key.txt",extract_key($Kver));
$plainText = "53965C38-E950-231A-8417-074BD95744A4:22-434-565-54544:".extract_key($Kp);
file_put_contents("plain.txt",$plainText);
// Signing
$hash = new Crypt_Hash('sha256');
$rsa = new Crypt_RSA();
$rsa->loadKey($KSign);
$rsa->setSignatureMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$rsa->setHash('sha256');
$signature = $rsa->sign($plainText);
$signedHS = base64_encode($signature);
file_put_contents("signedkey.txt", $signedHS);
// Verification
$signature = base64_decode($signedHS);
$rsa->loadKey($Kver);
$status = $rsa->verify($plainText, $signature);
var_dump($status);
Java code:
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.File;
import java.io.FileReader;
import java.math.BigInteger;
import java.security.spec.X509EncodedKeySpec;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class VerifySig {
public static RSAPublicKey fromPKCS1Encoding(byte[] pkcs1EncodedPublicKey) {
// --- parse public key ---
org.bouncycastle.asn1.pkcs.RSAPublicKey pkcs1PublicKey;
try {
pkcs1PublicKey = org.bouncycastle.asn1.pkcs.RSAPublicKey
.getInstance(pkcs1EncodedPublicKey);
} catch (Exception e) {
throw new IllegalArgumentException(
"Could not parse BER PKCS#1 public key structure", e);
}
// --- convert to JCE RSAPublicKey
RSAPublicKeySpec spec = new RSAPublicKeySpec(
pkcs1PublicKey.getModulus(), pkcs1PublicKey.getPublicExponent());
KeyFactory rsaKeyFact;
try {
rsaKeyFact = KeyFactory.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("RSA KeyFactory should be available", e);
}
try {
return (RSAPublicKey) rsaKeyFact.generatePublic(spec);
} catch (InvalidKeySpecException e) {
throw new IllegalArgumentException(
"Invalid RSA public key, modulus and/or exponent invalid", e);
}
}
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
String pkey = fromFile("pub_verify_key.txt");
byte[] dpkey = Base64.decodeBase64(pkey);
RSAPublicKey publicKey = fromPKCS1Encoding(dpkey);
String plainData = fromFile("plain.txt");
String data = fromFile("signedkey.txt");
byte[] ciphertext = Base64.decodeBase64(data);
System.out.println(new String(plainData.getBytes(), UTF_8));
verifyBC(publicKey, plainData, ciphertext);
System.out.flush();
}
private static void verifyBC(PublicKey publicKey, String plainData,
byte[] ciphertext) throws Exception {
// what should work (for PKCS#1 v1.5 signatures), requires Bouncy Castle provider
//Signature sig = Signature.getInstance( "SHA256withRSAandMGF1");
Signature sig = Signature.getInstance( "SHA256withRSA");
sig.initVerify(publicKey);
sig.update(plainData.getBytes(UTF_8));
System.out.println(sig.verify(ciphertext));
}
private static String fromFile(String filename) {
StringBuilder builder = new StringBuilder(8000);
try {
FileReader reader = new FileReader(new File(filename));
int c;
while((c = reader.read()) != -1) {
builder.append((char)c);
}
} catch(IOException ioe) {
throw new RuntimeException(ioe);
}
return builder.toString();
}
}
Related
I am trying to understand Jason Web Token with RSA Public Key & Private Key pairs.
I generated a RSA key pairs using this websites - https://travistidwell.com/jsencrypt/demo/
, and saved those in two different files-
private key "my_key" is as
-----BEGIN RSA PRIVATE KEY-----
MIICWgIBAAKBgFrSlpibGae4QZjcQp20b9+go22JF9WHJU4TLfdwJbumIgHlukD9
S/W7Dr1j4AXtJ0yJopdnf0bY4294SjjMO4DFHrLDEAB8ZeZkMhfyparYWKHECV5e
szvR6dIHGeDc1CoJolAsPmpO+0qfK4LfkesmLu+diIy9I+B2KJAAka6hAgMBAAEC
gYAlsQuqnYOSJVej1pUW2dEr34CzbpejmAiVVERZUgN20sV+QBaB7hzeCBlf49kO
3JLYoq4FY4BgqJYKpsM2uxteIC8dLJdayyNze1mnPiPW3wLR2bzedOJdjcY0H3Ju
M7lZlk9NVnBI7PNXw4hanifCGY2XPKyrOwrqrMgFsC382QJBAKlKAErRU9N8LZgq
TNAhMTaTPvpZuK4LqqV8RCqPFuNz1tVsO/cjGHfrU3R1pM619K6xCUjvwYvXL0MM
6vm/CKsCQQCJV6/bFuc4L+Uyik0Q+zaRz3fKi5h/AWoN1vmBcXQ01F5GtTAlZDc6
lk/rBk4M69k899I95ReMlIfc68S51P3jAkAp5eLEoaI5iVZPfsicClr/wtmnZGVM
zh5h7quATQHBMX5OPAdrVwhLRbbV7/fmISp5wd8mahBg59UOpzfQr/MZAkAtgC9y
lhPkOXnlVIxTo+ZgSCuXnsfWy9Em5KGkkMG+/tx88GoS+TCS6Flxs5UIEtrVqASv
HMbAfDTGrBVwu2+hAkAnrAHRhFlC8O9zAhoFkWcaYeFu+y1pBcF3pzFfQBpn6uK8
Xd0Ln+eMNaYl6lAECG36jtUvnKLkyfWMHzqdkERj
-----END RSA PRIVATE KEY-----
public key "my_key.pub" is as
-----BEGIN PUBLIC KEY-----
MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgFrSlpibGae4QZjcQp20b9+go22J
F9WHJU4TLfdwJbumIgHlukD9S/W7Dr1j4AXtJ0yJopdnf0bY4294SjjMO4DFHrLD
EAB8ZeZkMhfyparYWKHECV5eszvR6dIHGeDc1CoJolAsPmpO+0qfK4LfkesmLu+d
iIy9I+B2KJAAka6hAgMBAAE=
-----END PUBLIC KEY-----
When I am using this key in NodeJS as -
var fs = require('fs');
var JWT = require('jsonwebtoken');
var privateKey = fs.readFileSync('./my_key', 'utf8');
var publicKey = fs.readFileSync('./my_key.pub', 'utf8');
var jwtOptions = {
issuer : 'Chittaranjan Sardar',
subject: 'JWT Cross Platforms',
audience: 'https://github.com/crsardar',
expiresIn: '5m',
algorithm: "RS256"
};
let token = JWT.sign({user: 'CRSARDAR'}, privateKey, jwtOptions);
console.log("generated token = " + token);
var verifyResult = JWT.verify(token, publicKey, jwtOptions);
console.log("Verification has passed : " + JSON.stringify(verifyResult));
It is Working Fine.
But, when I am trying to use in Java, as follows -
package com.crsardar.handson.java.springboot.jwt.controller;
import java.io.File;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
public class JWTJavaWithPublicPrivateKey
{
public static void main(String[] args)
{
System.out.println("generating keys");
Map<String, Object> rsaKeys = null;
try
{
rsaKeys = getRSAKeys();
}
catch (Exception e)
{
e.printStackTrace();
}
PublicKey publicKey = (PublicKey) rsaKeys.get("public");
PrivateKey privateKey = (PrivateKey) rsaKeys.get("private");
System.out.println("generated keys");
String token = generateToken(privateKey);
System.out.println("Generated Token:\n" + token);
verifyToken(token, publicKey);
}
public static String generateToken(PrivateKey privateKey)
{
String token = null;
try
{
Instant now = Instant.now();
Instant after = now.plus(Duration.ofMinutes(1));
Date date = Date.from(after);
Claims claims = Jwts.claims();
claims.put("user", "CRSARDAR");
claims.put("issuer", "Chittaranjan Sardar");
claims.put("subject", "JWT Cross Platforms");
claims.put("audience", "https://github.com/crsardar");
claims.put("created", new Date());
JwtBuilder jwtBuilder = Jwts.builder();
jwtBuilder.setClaims(claims);
jwtBuilder.setExpiration(date);
jwtBuilder.signWith(SignatureAlgorithm.RS512, privateKey);
token = jwtBuilder.compact();
}
catch (Exception e)
{
e.printStackTrace();
}
return token;
}
// verify and get claims using public key
private static Claims verifyToken(String token, PublicKey publicKey)
{
Claims claims;
try
{
JwtParser jwtParser = Jwts.parser();
jwtParser.setSigningKey(publicKey);
Jws<Claims> claimsJws = jwtParser.parseClaimsJws(token);
claims = claimsJws.getBody();
System.out.println("verifyToken : issuer = " + claims.get("issuer"));
}
catch (Exception e)
{
claims = null;
}
return claims;
}
private static Map<String, Object> getRSAKeys() throws Exception
{
Map<String, Object> keys = new HashMap();
PrivateKey privateKey = getPrivateKey();
PublicKey publicKey = getPublicKey();
keys.put("private", privateKey);
keys.put("public", publicKey);
return keys;
}
private static PrivateKey getPrivateKey() throws Exception
{
ClassLoader classLoader = JWTJavaWithPublicPrivateKey.class.getClassLoader();
URL resource = classLoader.getResource("my_key");
File file = new File(resource.getFile());
byte[] keyBytes = Files.readAllBytes(Paths.get(file.toURI()));
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(spec);
}
private static PublicKey getPublicKey() throws Exception
{
ClassLoader classLoader = JWTJavaWithPublicPrivateKey.class.getClassLoader();
URL resource = classLoader.getResource("my_key.pub");
File file = new File(resource.getFile());
byte[] keyBytes = Files.readAllBytes(Paths.get(file.toURI()));
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(spec);
}
}
It is giving me following errors -
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: invalid key format
at sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(RSAKeyFactory.java:217)
at java.security.KeyFactory.generatePrivate(KeyFactory.java:372)
at com.crsardar.handson.java.springboot.jwt.controller.JWTJavaWithPublicPrivateKey.getPrivateKey(JWTJavaWithPublicPrivateKey.java:126)
at com.crsardar.handson.java.springboot.jwt.controller.JWTJavaWithPublicPrivateKey.getRSAKeys(JWTJavaWithPublicPrivateKey.java:110)
at com.crsardar.handson.java.springboot.jwt.controller.JWTJavaWithPublicPrivateKey.main(JWTJavaWithPublicPrivateKey.java:35)
Caused by: java.security.InvalidKeyException: invalid key format
at sun.security.pkcs.PKCS8Key.decode(PKCS8Key.java:331)
at sun.security.pkcs.PKCS8Key.decode(PKCS8Key.java:357)
at sun.security.rsa.RSAPrivateCrtKeyImpl.<init>(RSAPrivateCrtKeyImpl.java:91)
at sun.security.rsa.RSAPrivateCrtKeyImpl.newKey(RSAPrivateCrtKeyImpl.java:75)
at sun.security.rsa.RSAKeyFactory.generatePrivate(RSAKeyFactory.java:316)
at sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(RSAKeyFactory.java:213)
I have key.json data in which the following information is stored:
{
"privateExponent":"0x74326408a9392dc21ab4297391a8c2152c165ca71a3f2282ded681f2cbf8c999eb27bb17524edf431004fd5c5c4eae82ee9138d5bebd61b80f497f762a8bace0baaee6a374f94b27ee4824ebacbfed15d568f9cc17b369af3f0ad879c1d442a2401c01687d7ea51e64f5e8ca67437d4c591a699604af0adca695761561844527002ae51dd0c5a93217a1c6022c97091761837fb8341a6f85254a29bc2d3e48791a6347701a7743760546530fbe236c9bf90f9994ea428777b065c92dfd1bfa86796f43e1e2a1b47299e52c61620ad4ebe26b9bacec78ca73e66efa9404628a550c29ea59eb9826de5342da7b84bba6bcd50aa0fe267eaa8d113fab76262d4fe9",
"publicExponent": "0x100010",
"modulus": "0x00e1de9b838b4b2026b29f03d8fecb916622b25dd89d317d5e79ba2a3e148b2d73278cb1944ba4be4bf87f9ab03f612cb28944bc45086a00a9f87ea489ff0ea866e5d6cf62654065d12967d05836b286d9d55d0fe67faa7b77d8c66346b76b0716946e5a96c64f180e1bc71881534d79eba75582bba448ad648cf93d59c8eeb738ea6bb9a94ffada4ecee846b3aa666ba0fb68c10b39ed65aa067046e970cf19d2a92b787643e54ce09e1c7459475aa6d4b89eb5032dcf7b8b80833f12a5c86cce46f3d2583feccc6243653f75c0412499a2edafddad31f70811bcf81343a49933c992d25efc1e522220ea9da6c5cf80d9ed63ff5c21a1cc7fb537b414e6708957"
}
The Information for privateExponent, public Exponent and modulus was retrievied from the pem file, where the keys were created by openssl:
openssl genrsa 2048 > key.pem
openssl rsa -text < key.pem
So, first I remove the starting "0x", since Hex Strings are stored without 0x in Java. Then, I want to convert them into a byte-array and generate a Key out of it. This is my code for doing so:
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import javax.xml.bind.DatatypeConverter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
/* Json Fomrats:
key.json:
privateExponent: hex-String,
public Exponent: hex-String,
modulus: hex-String
payload.json:
message: String,
sig {
modulus: hex String,
publicExponent: hex String,
signature: String
}
*/
public class Main {
public static String toHexString(byte[] array) {
return DatatypeConverter.printHexBinary(array);
}
public static byte[] toByteArray(String s) {
return DatatypeConverter.parseHexBinary(s);
}
public static void main(String[] args) {
JSONParser parser = new JSONParser();
try {
Object obj = parser.parse(new FileReader(new File((System.getProperty("user.dir") + "\\src\\com\\company\\key.json"))));
JSONObject jObj = (JSONObject) obj;
System.out.println(jObj.get("privateExponent").toString());
String privKeyS = jObj.get("privateExponent").toString().replace("0x", "");
System.out.println(privKeyS);
byte[] privKeyBytes = toByteArray(privKeyS);
System.out.println(privKeyBytes);
PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(privKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey key = keyFactory.generatePrivate(privKeySpec);
} catch (FileNotFoundException e1) {
e1.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
}
}
}
When I run the problem, I get the following error message:
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: invalid key format
at sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(RSAKeyFactory.java:217)
at java.security.KeyFactory.generatePrivate(KeyFactory.java:372)
at com.company.Main.main(Main.java:70)
Caused by: java.security.InvalidKeyException: invalid key format
Why, though? I do not see the why this format is not working.
You are trying to load a PKCS#8 encoded key, but you need to create the key from privateExponent and modulus
//Convert hex strings to BigInteger
BigInteger privateExponent = new BigInteger(privateExponentHex, 16);
BigInteger modulus = new BigInteger(modulusHex, 16);
//Build the private key
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPrivateKeySpec privateSpec = new RSAPrivateKeySpec(modulus, privateExponent);
PrivateKey privateKey = keyFactory.generatePrivate(privateSpec);
I have a public key returned like
"kty" : "RSA",
"alg" : "RS256",
"ext" : false,
"n": "vswzzDmrqLSHUu61YDxUhM87hjcVjg42NwpFOyLQK8CyW5YRcr1YUkFRNDbb92MTNW3CsSWJX3DSuilnxf8n3_JW-A9R5JAqwmEygYIXuFcoJ_pb923bph0-ayWPBfD-qwYrELvpiEHBf1QSLJYkRb1wzAlwhCeYJorifu2WhCZoOVVYQAEyNqYF7AVhNImioT8-lhFWGqHp2Jt7-oXtCjVVyyShRHUMYyCRzGj1VGI6AU5DgVebXYD2GJawUhX -AD2CzsX8lMXeaVu88sBU9XLL1Zb_cOvAC7wTXxcls0taKx-8PiWUWKjSg0-O2ZXbfFROyQpQYHQH0BkO8XRh8w"
"e" : "AQAB"
And, I want to use java to load it, and my code is like
package key;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class PublicKeyReader {
public static PublicKey get() throws Exception {
String key = "vswzzDmrqLSHUu61YDxUhM87hjcVjg42NwpFOyLQK8CyW5YRcr1YUkFRNDbb92MTNW3CsSWJX3DSuilnxf8n3_JW-A9R5JAqwmEygYIXuFcoJ_pb923bph0-ayWPBfD-qwYrELvpiEHBf1QSLJYkRb1wzAlwhCeYJorifu2WhCZoOVVYQAEyNqYF7AVhNImioT8-lhFWGqHp2Jt7-oXtCjVVyyShRHUMYyCRzGj1VGI6AU5DgVebXYD2GJawUhX-AD2CzsX8lMXeaVu88sBU9XLL1Zb_cOvAC7wTXxcls0taKx-8PiWUWKjSg0-O2ZXbfFROyQpQYHQH0BkO8XRh8w";
X509EncodedKeySpec spec = new X509EncodedKeySpec(key.getBytes());
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(spec);
}
public static void main(String[] args) {
try {
new PublicKeyReader().get();
} catch (Exception e) {
e.printStackTrace();
}
}
}
and I got exception thrown, says java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: invalid key format
How to load it correctly?
Java only approach (look ma, no libraries):
package nl.owlstead.stackoverflow;
import java.io.File;
import java.math.BigInteger;
import java.nio.file.Files;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.RSAPublicKeySpec;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class LoadRSAKeyFromText {
public static void main(String[] args) throws Exception {
// parse the lines to find the modulus n and public exponent e
List<String> all = Files.readAllLines(new File(args[0]).toPath());
String nString = null, eString = null;
for (String line : all) {
Pattern nPattern = Pattern.compile("\"n\"\\s*:\\s*\"(.*?)\",?");
Matcher nMatcher = nPattern.matcher(line);
if (nMatcher.matches()) {
nString = nMatcher.group(1).replaceAll("\\s+", "");
}
Pattern ePattern = Pattern.compile("\"e\"\\s*:\\s*\"(.*?)\",?");
Matcher eMatcher = ePattern.matcher(line);
if (eMatcher.matches()) {
eString = eMatcher.group(1);
}
}
// decode base 64 (with _ and -, so URL safe)
Decoder urlDecoder = Base64.getUrlDecoder();
byte[] nData = urlDecoder.decode(nString);
byte[] eData = urlDecoder.decode(eString);
// convert to *positive* integers
BigInteger n = new BigInteger(1, nData);
BigInteger e = new BigInteger(1, eData);
// create RSA specification and convert to key
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(n, e);
KeyFactory kf = KeyFactory.getInstance("RSA");
RSAPublicKey pk = (RSAPublicKey) kf.generatePublic(keySpec);
System.out.println(pk.getAlgorithm());
}
}
Java doesn't know this kind of format so you have to parse it yourself, or find a decoder. I was lazy and programmed it.
Auth0 provides two JWT libraries, one for Node: node-jsonwebtoken, and one for Java: java-jwt.
I created the private/public key pair, and used it successfully in Node with node-jsonwebtoken:
var key = fs.readFileSync('private.key');
var pem = fs.readFileSync('public.pem');
var header = {...};
var payload = {...};
header.algorithm = "RS256";
var message = jsonwebtoken.sign(payload, key, header);
var decoded = jsonwebtoken.verify(message, pem, {algorithm: "RS256"});
But I found no way of doing the same in Java with java-jwt.
Anyone has a working example of how to use private/public keys for JWT in Java?
I used the following code for JWT in Java. Try it.
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
public class JWTJavaWithPublicPrivateKey {
public static void main(String[] args) {
System.out.println("generating keys");
Map<String, Object> rsaKeys = null;
try {
rsaKeys = getRSAKeys();
} catch (Exception e) {
e.printStackTrace();
}
PublicKey publicKey = (PublicKey) rsaKeys.get("public");
PrivateKey privateKey = (PrivateKey) rsaKeys.get("private");
System.out.println("generated keys");
String token = generateToken(privateKey);
System.out.println("Generated Token:\n" + token);
verifyToken(token, publicKey);
}
public static String generateToken(PrivateKey privateKey) {
String token = null;
try {
Map<String, Object> claims = new HashMap<String, Object>();
// put your information into claim
claims.put("id", "xxx");
claims.put("role", "user");
claims.put("created", new Date());
token = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.RS512, privateKey).compact();
} catch (Exception e) {
e.printStackTrace();
}
return token;
}
// verify and get claims using public key
private static Claims verifyToken(String token, PublicKey publicKey) {
Claims claims;
try {
claims = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token).getBody();
System.out.println(claims.get("id"));
System.out.println(claims.get("role"));
} catch (Exception e) {
claims = null;
}
return claims;
}
// Get RSA keys. Uses key size of 2048.
private static Map<String, Object> getRSAKeys() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
Map<String, Object> keys = new HashMap<String, Object>();
keys.put("private", privateKey);
keys.put("public", publicKey);
return keys;
}
}
Maven Dependency
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.6.0</version>
</dependency>
Recent versions (since 3.0.0) of the auth0 java-jwt library supports RSA and ECDSA for signing JWT tokens using a public/private key pair.
Example of signing a JWT using java-jwt (based on the documentation).
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Map;
class JwtPKSigningExample {
public static void main(String[] args) throws Exception {
Map<String, Object> keys = generateRSAKeys();
String token = null;
try {
RSAPrivateKey privateKey = (RSAPrivateKey) keys.get("private");
Algorithm algorithm = Algorithm.RSA256(null, privateKey);
token = JWT.create()
.withIssuer("pk-signing-example")
.sign(algorithm);
} catch (JWTCreationException x) {
throw x;
}
try {
RSAPublicKey publicKey = (RSAPublicKey) keys.get("public");
Algorithm algorithm = Algorithm.RSA256(publicKey, null);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("pk-signing-example")
.build();
DecodedJWT jwt = verifier.verify(token);
System.out.println(jwt.getToken());
} catch (JWTVerificationException x) {
throw x;
}
}
private static Map<String, Object> generateRSAKeys() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
return Map.of("private", keyPair.getPrivate(), "public", keyPair.getPublic());
}
}
That particular library doesn't support it. But you can check others for Java that do. See here: https://jwt.io/
package com.java;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Enumeration;
import org.jose4j.json.internal.json_simple.parser.ParseException;
import org.jose4j.jwk.JsonWebKeySet;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.MalformedClaimException;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.keys.resolvers.JwksVerificationKeyResolver;
import org.jose4j.keys.resolvers.VerificationKeyResolver;
import org.jose4j.lang.JoseException;
public class JWTSigningAndVerification {
public static void main(String args[]) throws Exception {
String jwt = generateJWT();
validateJWTwithJWKS(jwt);
}
private static String generateJWT() throws FileNotFoundException, KeyStoreException, IOException,
NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException, JoseException {
JwtClaims jwt_claims = new JwtClaims();
jwt_claims.setSubject("sub");
jwt_claims.setIssuer("https://domain");
jwt_claims.setIssuedAtToNow();
jwt_claims.setExpirationTimeMinutesInTheFuture(1000000);
jwt_claims.setGeneratedJwtId();
jwt_claims.setClaim("sid", "sessionid");
jwt_claims.setClaim("email", "test#mail.com");
jwt_claims.setClaim("given_name", "first");
jwt_claims.setClaim("family_name", "last");
JsonWebSignature jws = new JsonWebSignature();
jws.setPayload(jwt_claims.toJson());
String KeyPassword = "p12-key-password";
File file = new File("path-to-key.p12");
InputStream stream = new FileInputStream(file);
KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType());
store.load(stream, KeyPassword.toCharArray());
Enumeration e = store.aliases();
String alias = (String) e.nextElement();
PrivateKey key = (PrivateKey) store.getKey(alias, KeyPassword.toCharArray());
jws.setKey(key);
jws.setKeyIdHeaderValue("1");
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_PSS_USING_SHA512);
jws.setHeader("typ", "JWT");
String jwt = jws.getCompactSerialization();
System.out.println(jwt);
return jwt;
}
private static void validateJWTwithJWKS(String jwt) throws JoseException, FileNotFoundException, IOException,
ParseException, InvalidJwtException, MalformedClaimException {
JsonWebKeySet jsonWebKeySet = new JsonWebKeySet("json-jwks-escaped");
VerificationKeyResolver verificationKeyResolver = new JwksVerificationKeyResolver(jsonWebKeySet.getJsonWebKeys());
JwtConsumer jwtConsumer = new JwtConsumerBuilder().setVerificationKeyResolver(verificationKeyResolver).build();
JwtClaims claims = jwtConsumer.processToClaims(jwt);
System.out.println("sub:- " + claims.getSubject());
}
}
I'm trying to write gpg encryptor/decryptor (code below). In first step program encrypts "#data1\n#data2\n#data3\n" string and in next step decrypts the array of bytes that decryptor returns. But... why text from decryptor is not the same as "#data1\n#data2\n#data3\n" ? Where did I missed something ? Thanks for your help.
package tests.crypto;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.util.Iterator;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter;
import Decoder.BASE64Encoder;
public class GPGTest {
char[] pass = { 'p', 'a', 's', 's' };
public static void main(String[] args) throws Exception {
GPGTest gTest = new GPGTest();
gTest.setUpCipher();
}
public void setUpCipher() throws NoSuchAlgorithmException,
InvalidKeyException, IllegalBlockSizeException,
NoSuchProviderException, BadPaddingException,
NoSuchPaddingException {
System.out.println("--- setup cipher ---");
// public key
String publicKeyFilePath = "E:/Programs/Keys/GPG/Public/public.key";
File publicKeyFile = new File(publicKeyFilePath);
// secret key
String secretKeyFilePath = "E:/Programs/Keys/GPG/Secret/private.key";
File secretKeyFile = new File(secretKeyFilePath);
// security provider
Security.addProvider(new BouncyCastleProvider());
try {
// Read the public key
FileInputStream pubIn = new FileInputStream(publicKeyFile);
PGPPublicKey pgpPubKey = readPublicKey(pubIn);
PublicKey pubKey = new JcaPGPKeyConverter().getPublicKey(pgpPubKey);
// Read the private key
FileInputStream secretIn = new FileInputStream(secretKeyFile);
PGPSecretKey pgpSecretKey = readSecretKey(secretIn);
PGPPrivateKey pgpPrivKey = pgpSecretKey
.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(
new BcPGPDigestCalculatorProvider()).build(pass));
PrivateKey privKey = new JcaPGPKeyConverter()
.getPrivateKey(pgpPrivKey);
Cipher cipher = Cipher.getInstance("RSA");
// Encrypt data
byte[] encData = encryptData(cipher, pubKey, new String(
"#data1\n#data2\n#data3\n").getBytes());
String cryptString = new BASE64Encoder().encode(encData);
System.out.println("\n\nEncrypted data=" + cryptString);
// Decrypt data
byte[] decData = decryptData(cipher, privKey, encData);
String decryptString = new BASE64Encoder().encode(decData);
System.out.println("\n\nDecrypted data=" + decryptString);
} catch (Exception e) {
System.out.println("Setup cipher exception");
System.out.println(e.toString());
}
}
public byte[] encryptData(Cipher cipher, PublicKey pubKey, byte[] clearText) {
byte[] encryptedBytes = null;
try {
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
encryptedBytes = cipher.doFinal(clearText);
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return encryptedBytes;
}
public byte[] decryptData(Cipher cipher, PrivateKey privKey, byte[] encryptedText) {
byte[] decryptedBytes = null;
try {
cipher.init(Cipher.DECRYPT_MODE, privKey);
decryptedBytes = cipher.doFinal(encryptedText);
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return decryptedBytes;
}
private static PGPPublicKey readPublicKey(InputStream input) throws IOException, PGPException {
PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(input));
Iterator<?> keyRingIter = pgpPub.getKeyRings();
while (keyRingIter.hasNext()) {
PGPPublicKeyRing keyRing = (PGPPublicKeyRing) keyRingIter.next();
Iterator<?> keyIter = keyRing.getPublicKeys();
while (keyIter.hasNext()) {
PGPPublicKey key = (PGPPublicKey) keyIter.next();
if (key.isEncryptionKey()) {
return key;
}
}
}
throw new IllegalArgumentException(
"Can't find encryption key in key ring.");
}
private static PGPSecretKey readSecretKey(InputStream input) throws IOException, PGPException {
PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(input));
Iterator<?> keyRingIter = pgpSec.getKeyRings();
while (keyRingIter.hasNext()) {
PGPSecretKeyRing keyRing = (PGPSecretKeyRing) keyRingIter.next();
Iterator<?> keyIter = keyRing.getSecretKeys();
while (keyIter.hasNext()) {
PGPSecretKey key = (PGPSecretKey) keyIter.next();
if (key.isSigningKey()) {
return key;
}
}
}
throw new IllegalArgumentException(
"Can't find signing key in key ring.");
}
}
They aren't the same because you are base-64 encoding the results of the decryption, rather than decoding it as text. Do this instead:
byte[] decData = decryptData(cipher, privKey, encData);
System.out.println("\n\nDecrypted data=" + new String(decData));
It is poor practice to use the platform default character encoding. Instead, you should convert text to bytes with with specific encoding, and use the same coding when decoding the bytes.
byte[] plaintext = "#data1\n#data2\n#data3\n").getBytes(StandardCharsets.UTF_8);
...
String recovered = new String(decData, StandardCharsets.UTF_8);