We need to handle requests from an API gateway. The identity of the user will be passed to us through and encrypted (by private key) JWT token and we received the public key (in Base64) to validate it.
For now I've been failing to do that.
What I did manage to do is to encrypt and then again validate my own token using a self generated private/public key:
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
kp = kpg.generateKeyPair();
//generate & sign simple token:
Map<String, Object> claims = new HashMap<>();
claims.put("id", "xxx");
claims.put("role", "user");
token = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.RS256, kp.getPrivate()).compact();
//now sign the thing
Claims claims = Jwts.parser().setSigningKey(kp.getPublicKey()).parseClaimsJws(base64EncodedToken).getBody();
System.out.println(claims.get("id")); //gives me xxx as expected
No issue here.
Next step was to externalise (file) those generated keys; e.g.:
out = new FileWriter("key.pub");
out.write(encoder.encodeToString(kp.getPublic().getEncoded()));
out.close();
And then instead of using a KeyPairGenerator create the Public/Private keys by reading them from the (Base64) file and decoding them:
public PrivateKey getPrivateKey() throws Exception {
String base64PrivateKey = new String(Files.readAllBytes(Paths.get(ClassLoader.getSystemResource("private.key").toURI())));
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec (Base64.getDecoder().decode(base64PrivateKey.getBytes()));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(keySpec);
}
public PublicKey getPublicKey() throws Exception {
String base64PublicKey = new String(Files.readAllBytes(Paths.get(ClassLoader.getSystemResource("public.key").toURI())));
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(base64PublicKey.getBytes()));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(keySpec);
}
When I then run the same process again (generate encrypted token & validate it):
Exception in thread "main" io.jsonwebtoken.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.
I must be missing something since I have little experience with encryption.
P.S. The files are 1 liners likes this:
public.key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxetc3258...
private.key: MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEA...
Related
i'm trying to create a JWT and for that i need a RSA key. I already have a working Java code, but i have to get it working in SWIFT. i have a private key, that is used in the java version to create the RSA key the following way:
private static RSAPrivateKey getPrivateKey(String key) {
PrivateKey privateKey = null;
final String algorithm = "RSA";
try {
byte[] keyBytes = Base64.decodeBase64(key.getBytes());
KeyFactory kf = KeyFactory.getInstance(algorithm);
EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
privateKey = kf.generatePrivate(keySpec);
} catch (Exception e) {
}
}
this key is then used to create the Algorithm Algorithm alg = Algorithm.RSA256(null, privateKey) which is then used to sign the JWT token.
The classes used here are from java.security, com.auth0 and org.apache.commons.codec
What i tried in swift is putting the key(the one that is given to the above function) into a file
-----BEGIN PRIVATE KEY-----
[the actual key]
-----END PRIVATE KEY-----
and then call
let path = Bundle.main.path(forResource: "privateKey", ofType: "key")
let privateKeyPath = URL(fileURLWithPath: path!)
let privateKey: Data = try Data(contentsOf: privateKeyPath, options: .alwaysMapped)
let jwtSigner = JWTSigner.rs256(privateKey: privateKey)
let signedJwt = try jwt.sign(using: jwtSigner)
but this gives me the error
"Provided private key could not be used to sign JWT", internalError: SwiftJWT.JWTError.(unknown context at $1047b9eec).InternalError.invalidPrivateKey)
the library i'm using here is https://github.com/Kitura/Swift-JWT.git
can anyone tell me what i'm doing wrong?
I had generate public key using Java Spring Security, but I can not use that public key to encrypt the data using Nodejs crypto library. I think it is because of its format(X509).
My Nodejs code
module.exports.encryptRsa = (toEncrypt, pemPath) => {
let absolutePath = path.resolve(pemPath);
let publicKey = fs.readFileSync(absolutePath, "utf8");
let buffer = Buffer.from(toEncrypt);
let encrypted = crypto.publicEncrypt(publicKey, buffer);
return encrypted.toString("base64");
};
My Java code
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(keyAlgorithm);
keyGen.initialize(2048);
KeyPair keyPair = keyGen.genKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
byte[] privateKeyBytes = privateKey.getEncoded();
byte[] publicKeyBytes = publicKey.getEncoded();
String formatPrivate = privateKey.getFormat(); // PKCS#8
String formatPublic = publicKey.getFormat(); // X.509
FileWriter fos = new FileWriter("publicKey.pem");
fos.write("-----BEGIN RSA PUBLIC KEY-----\n");
fos.write(enc.encodeToString(publicKeyBytes));
fos.write("\n-----END RSA PUBLIC KEY-----\n");
fos.close();
Java's getEncoded() method returns the public key in format called 'spki' by Node crypto. Java's name for that format is "X.509", an unfortunate choice because it causes confusion with certificates of that name.
The proper PEM header for spki keys is simply -----BEGIN PUBLIC KEY-----. Just get rid of RSA in the header and footer.
I receive the jwt (access token) from an OAuth Server. The OAuth server has already provided me with the secret, public key, and self-signed CA certificate.
I want to write a code that when I receive a jwt, I can validate it and check if this server has sent it to me. I use the following code to validat my jwt in java.
String jwt = "xxx.yyy.zzz";
//This line will throw an exception if it is not a signed JWS (as expected)
Claims claims = Jwts.parser().setSigningKey(DatatypeConverter.parseBase64Binary(self_signed_CA_Certificate))
.parseClaimsJws(jwt).getBody();
I get the error: Key bytes cannot be specified for RSA signatures. Please specify a PublicKey or PrivateKey instance.
Any help is appreciated.
I had the same problem, it was my solution
in my case I gad a certificate that give the ability that can be signed with different keys,
CertificateFactory fact = CertificateFactory.getInstance("X.509");
FileInputStream is = new FileInputStream ("yourcertificate.jks");
Certificate cer = fact.generateCertificate(is);
PublicKey publicKey = cer.getPublicKey();
and then to verify the jwt token used:
Claims body = Jwts.parser()
.setSigningKey(publicKey)
.parseClaimsJws(token)
.getBody();
you have to create secretKey using RSA like
SecretKey secretKey = KeyGenerator.getInstance("AES").generateKey();
or
Key secretKey = MacProvider.generateKey();
use same secretKey key to generate jwt string and decrypt it using same secrete key like
SecretKey secretKey = KeyGenerator.getInstance("AES").generateKey();
String compactJws = Jwts.builder()
.setSubject("Joe")
.signWith(SignatureAlgorithm.HS512, secretKey)
.compact();
Claims claims = Jwts.parser().setSigningKey(secretKey)
.parseClaimsJws(compactJws).getBody();
I'm trying to sign an encrypted message with a private key and verify it in Java. This is my first time working with encryption and signatures so I'm not sure how it is supposed to work and I'm kind of stuck here. The verification always returns false.
Here I sign the message:
public byte[] rsaSign (byte[] data) {
byte[] cipherData = null;
try {
RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(signModulus, signExponent);
KeyFactory fact = KeyFactory.getInstance("RSA");
PrivateKey privKey = fact.generatePrivate(keySpec);
Signature s = Signature.getInstance("SHA1withRSA");
s.initSign(privKey);
s.update(data);
return s.sign();
}
return cipherData;
}
And here I try to verify the signature:
public boolean rsaVerify (byte[] data, byte[] signature) {
boolean success = false;
try {
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(signModulus, signPublicExponent);
KeyFactory fact = KeyFactory.getInstance("RSA");
PublicKey pubKey = fact.generatePublic(keySpec);
Signature s = Signature.getInstance("SHA1withRSA");
s.initVerify(pubKey);
s.update(data);
success = s.verify(signature);
return success;
}
return false;
}
Can anyone see a problem? The keys are generated in C# and converted to BigIntegers in java.
Signature verification is failed because you are using a different public key in the verification method.
Use the public key to verify the signature which is consistent with the private key that is used into rsaSign() method.
Hope this will help you. Note that, this public key is consistent with the private key which is used in Signature Generation method :
/**
* This method will sign message with RSA 2048 key
* #return Void
*/
public void rsaSign (String message) throws Exception {
//key generation
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN");
keyGen.initialize(2048, random);
KeyPair keyPair = keyGen.generateKeyPair();
PrivateKey priv = keyPair.getPrivate();
PublicKey pub = keyPair.getPublic();
System.out.println("RSAPub key Mod for Sign/Verify : " + Helper.toHex(((RSAPublicKey)pub).getModulus().toByteArray()));
System.out.println("RSAPub key Exp for Sign/Verify : " + Helper.toHex(((RSAPublicKey)pub).getPublicExponent().toByteArray()));
//sign
Signature dsa = Signature.getInstance(signALG);
dsa.initSign(priv);
dsa.update(Helper.toByte(message));
byte[] realSig = dsa.sign();
System.out.println("RSA Sign-Data : " + Helper.toHex(realSig));
}
/**
* This method verify signature with RSA public key
* #param message The plain message
* #param rsaMOD RSA Public key Modulus in string
* #param rsaEXP RSA Public key Exponent in string
* #param rsaSignData Signature which will be verified
* #return true if verifications success, false otherwise
*/
public boolean rsaVerify(String message, String rsaMOD, String rsaEXP, String rsaSignData) throws Exception {
BigInteger modBigInteger = new BigInteger(Helper.toByte(rsaMOD));
BigInteger exBigInteger = new BigInteger(Helper.toByte(rsaEXP));
RSAPublicKeySpec spec = new RSAPublicKeySpec(modBigInteger, exBigInteger);
KeyFactory factory = KeyFactory.getInstance("RSA");
PublicKey publicKey = factory.generatePublic(spec);
Signature signature = Signature.getInstance(signALG);
signature.initVerify(publicKey);
signature.update(Helper.toByte(message));
return signature.verify(Helper.toByte(rsaSignData));
}
You should try and test these things locally first, with your own generated key pair. If that fails your code is wrong - it's a very simple wrapper around Java Signature so that's not at all that likely.
You already used a complete specification of the signature algorithm, so provider defaults are not an issue here.
Then check the correctness of the data on both sides by printing it out in Hex or Base64 right before signature generation/verification. If that fails you've got an I/O or encoding/decoding error. Encoding/decoding errors & string handling make up about 30% of the total of cryptography related questions!
Finally you could obtain and compare the modulus of the private and public key. If the moduli don't match then you're using a private and public key of a different key pair, and signature verification will of course always fail.
My application receives the raw pieces of a public RSA key (n and e) and needs to use these to encrypt a cipher text. I've been trying to use BouncyCastle but my code isn't working. The problem arises in trying to create the X509EncodedKeySpec.
Can anyone help me get this working? Here's the code I have:
public static PublicKey getPublicKeyFromString(String key) throws Exception
{
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(Base64Encoder.decode(key));
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
return publicKey;
}
I guess the real problem is that n and e are separate and I don't know how to combine them.
Why are you not using new RSAPublicKeySpec(n,e)?
public static PublicKey getPublicKeyFromString(String key) throws Exception
{
BASE64Decoder b64 = new BASE64Decoder();
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(b64.decodeBuffer(key));
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
return publicKey;
}