how do i sign my JWT with rsa key in swift? - java

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?

Related

Decoding and validating JWS token using public key

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...

How to Hash a given data using clients digital signature in java SHA 256 Algorithm

We want to Hash a data using clients digital signature using java sha 256 bit hashing algorithm.
How can we add digital signature while hashing in java.
If I'm understanding correctly, you want to sign some data. Here is a sample method:
public static String encode(String dataToEncode, String secret) throws InvalidKeyException, NoSuchAlgorithmException {
byte[] decodedSecret = Base64.getDecoder().decode(secret);
SecretKeySpec keySpec = new SecretKeySpec(decodedSecret, "HmacSHA256");
Mac sha256 = Mac.getInstance("HmacSHA256");
sha256.init(keySpec);
return Base64.getEncoder().encodeToString(sha256.doFinal(dataToEncode.getBytes()));
}
The secret is the Base64 encoded secret key. The method returns the Base64 encoded hash of the data. The Base64 part is optional, you can remove it if you don't need that encoding. This is a method I use when signing REST API calls to crypto exchanges.
The following solution signs a String by applying an RSA PKCS#8 formatted private key. If your code has read the private key as a text from a pem file that looks like the following example:
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDRxFWXGYDG8zKw
ihIS+Ydh/nWX9NwkFTKMRjH8BQ78ZEnXrnGJHvd+dI+zEiRo7rLuDXMOjsnhIR/O
....
+wqssDAApq+CiPcBnn0x2Vw=
-----END PRIVATE KEY-----
Then you need to strip out the first and last lines and all the new line characters ('\n'). If your privateKey is read (from a java keystore for example) you can remove the lines of code that convert the String of private key into java.security.PrivateKey object.
private static String signSHA256RSA(String inputStr, String inputKey) throws Exception {
String key = inputKey.replaceAll("-----END PRIVATE KEY-----", "")
.replaceAll("-----BEGIN PRIVATE KEY-----", "")
.replaceAll("\n", "");
byte[] keyBytes = Base64.getDecoder().decode(key);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey privateKey = kf.generatePrivate(spec);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(inputStr.getBytes("UTF-8"));
byte[] s = signature.sign();
return Base64.getEncoder().encodeToString(s);
}

I had generate Public Key by Java spring security and I try to use that public key to encrypt data with Nodejs Crypto but it return error

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.

How generate a valid private (RSA 1024) key for a tor onion service using java?

I'm trying to generate a valid private key for a tor onion service in java. With this private key I want to get a valid .onion address.
I have run various combinations (with this bit/without that bit) of the code below
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PrivateKey privateKeyGenerated = keyPair.getPrivate();
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyGenerated.getEncoded()));
Base64.Encoder encoder = Base64.getEncoder();
String privateKeyEncoded = encoder.encodeToString(privateKey.getEncoded());
String fileName = "{{where I'm wanting to store the file}}";
Writer writer = new FileWriter(fileName);
writer.write("-----BEGIN RSA PRIVATE KEY-----\n");
writer.write(privateKeyEncoded);
writer.write("\n-----END RSA PRIVATE KEY-----\n");
writer.close();
After generation I copy the key to my /var/lib/tor/hidden_service/private_key, remove any associated hostname and start the tor service. In the logs I get the error:
TLS error: wrong tag (in asn1 encoding routines:ASN1_CHECK_TLEN:---)
TLS error: nested asn1 error (in asn1 encoding routines:ANS1_D2I_EX_PRIMITIVE:---)
TLS error: nested asn1 error (in asn1 endoding routines:ASN1_TEMPLATE_NOEXP_D2I:---)
TLS error: RSA lib (in rsa routines:OLD_RSA_PRIV_DECODE:---)
If a resulting .onion address is generated it doesn't work.
How do I generate a valid private key?
Solution: Change BEGIN RSA PRIVATE KEY with BEGIN PRIVATE KEY
Java encodes the key IN PKCS#8 format
PrivateKey privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyGenerated.getEncoded()));
But you are generating a PEM file with the header -----BEGIN RSA PRIVATE KEY----- which is reserved to PKCS#1 keys (old format but very common), and .onion is assuming that it is pkcs1 when it really is pkcs8. See the error
TLS error: RSA lib (in rsa routines:OLD_RSA_PRIV_DECODE:---)
So you need to use the PCKS#8 header -----BEGIN PRIVATE KEY-----
See also this post Load a RSA private key in Java (algid parse error, not a sequence)

RSA Encryption Javascript and Decrypt Java

Spent almost 2 days with different combinations.I am generating a asymmetric key pair (public and private) in java using RSA algorithm and trying to use the public key in javascript to encrypt some text and decrypt back in java on server side. I am getting "javax.crypto.IllegalBlockSizeException: Data must not be longer than 128 bytes" exception while trying to decrypt back the string encrypted in javascript. Would appreciate some help...
Using thi Javascript library to encrypt.
https://github.com/wwwtyro/cryptico
var publicKeyString = ""// base64encoded public key string generated in java
Here is my javascript code
var EncryptionResult = cryptico.encrypt("somestring", publicKeyString);
console.log("Encrypted status-"+EncryptionResult.status);
console.log("Encrypted String-"+EncryptionResult.cipher);
It is successfully encrypting the string.
Java Key Generation and Decryption
Cipher cipher = Cipher.getInstance("RSA");
KeyFactory fact = KeyFactory.getInstance("RSA");
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024); // 1024 used for normal
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
FileOutputStream fos = null;
ObjectOutputStream oos = null;
Code to store the private key in file which is used to decrypt in decrypt method.
RSAPrivateKeySpec rsaPrivKeySpec = fact.getKeySpec(privateKey,
RSAPrivateKeySpec.class);
System.out.println("Writing private key...");
fos = new FileOutputStream(PRIVATE_KEY_FILE);
oos = new ObjectOutputStream(new BufferedOutputStream(fos));
oos = new ObjectOutputStream(new BufferedOutputStream(fos));
oos.writeObject(rsaPrivKeySpec.getModulus());
oos.writeObject(rsaPrivKeySpec.getPrivateExponent());
oos.close();
Decrypt method
public String decrypt(String ciphertext)
throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException
{
if (ciphertext.length() == 0) return null;
byte[] dec = org.apache.commons.codec.binary.Base64.decodeBase64(ciphertext);
try {
System.out.println("Private Key file name----"+PRIVATE_KEY_FILE);
privateKey = readPrivateKeyFromFile(PRIVATE_KEY_FILE);
} catch (IOException e) {
e.printStackTrace();
}
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decrypted = cipher.doFinal(dec);
return new String(decrypted, PLAIN_TEXT_ENCODING);
}
//reading private key from file
public PrivateKey readPrivateKeyFromFile(String fileName)
throws IOException {
FileInputStream fis = null;
ObjectInputStream ois = null;
try {
fis = new FileInputStream(new File(fileName));
ois = new ObjectInputStream(fis);
System.out.println("Private Key file-"+fileName);
BigInteger modulus = (BigInteger) ois.readObject();
BigInteger exponent = (BigInteger) ois.readObject();
// Get Private Key
RSAPrivateKeySpec rsaPrivateKeySpec = new RSAPrivateKeySpec(modulus, exponent);
KeyFactory fact = KeyFactory.getInstance("RSA");
PrivateKey privateKey = fact.generatePrivate(rsaPrivateKeySpec);
return privateKey;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ois != null) {
ois.close();
if (fis != null) {
fis.close();
}
}
}
return null;
}
From the Cryptico documentation it seems that it is not a simple RSA encryption, but a complex operation that generates AES key, encrypts it with RSA, encrypts the data with AES and outputs a concatenation of encrypted AES key and encrypted data. If you want to decrypt that in Java you will have to check the Cryptico source code and reimplement the same in Java.
As for your current attempt and javax.crypto.IllegalBlockSizeException: Data must not be longer than 128 bytes error:
When you do not specify the full transformation the default JCE transformation for RSA is RSA/ECB/PKCS1Padding.
In this mode the RSA encrypts or decrypts a single block of data which the length is not greater than the size of the key (more specifically, if the input sequence of bytes is interpreted as a big integer, its value should be less that the modulus used by the RSA). You can find additional information in this and this questions.
With the key size of 1024 bits the maximum data size is 128 bytes, and that is exactly what the exception says because the output of Cryptico is obviously not a single RSA block and its length is greater that expected by "plain" RSA. Trying to use some other cipher mode or padding mode in Java will not help in that situation either.
Thanks Oleg for the detailed information. I will definitely take a look into it.
For now I switched to jsencrypt and it seems to work fine.
https://github.com/travist/jsencrypt
EDIT
How you get the encoded public key for the js encrypt?
Here is the solution for data Encryption from JS and Decrypt on Java(server side). I have used Cryptico js library for encryption(http://wwwtyro.github.io/cryptico/).
First of all we have to generate the java Keystore file from your local system. Don't use other Keystore files such as online Keystore. For creating the java Keystore(JKS) you can use KeyStore Explorer tool.
Below is the config I have used, using KeyStore Explorer tool
Keystore type - JKS
RSA algorithm - Keysize 1024
Version - version 3
Signature algorithm - SHA256 with RSA
Validity period - 99 years(based on your requirement)
Name filed - Fill all the mandatory fields - remember the "alias" and "password" what you entered here.
Finally, save the file as .jks on your local system.
Step-1
we have to use this Keystore file on the java side and we send the public key to the frontend.
I have created the service class which is responsible to load Keystore from the keystore file path(string), Keypair and Decrypt. You have to provide the alias, password, keystore type.
public KeyPair getExistingKeyStoreKeyPair(String keystorePath){
KeyPair generateKeyPair = null
try {
File file = new File(keystorePath)
KeyStore keyStore = loadKeyStore(file, "password", "JKS")
generateKeyPair = getKeyPair(keyStore, "fin360", "password")
} catch (Exception ex){
println(ex)
}
return generateKeyPair
}
public KeyStore loadKeyStore(final File keystoreFile, final String password, final String keyStoreType) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
if (null == keystoreFile) {
throw new IllegalArgumentException("Keystore url may not be null")
}
final URI keystoreUri = keystoreFile.toURI()
final URL keystoreUrl = keystoreUri.toURL()
final KeyStore keystore = KeyStore.getInstance(keyStoreType)
InputStream is = null
try {
is = keystoreUrl.openStream();
keystore.load(is, null == password ? null : password.toCharArray())
} finally {
if (null != is) {
is.close()
}
}
return keystore;
}
public KeyPair getKeyPair(final KeyStore keystore, final String alias, final String password) {
PublicKey publicKey
PrivateKey privateKey
Key key
KeyPair keyPair
try {
key = (PrivateKey) keystore.getKey(alias, password.toCharArray())
final Certificate cert = keystore.getCertificate(alias)
publicKey = cert.getPublicKey()
privateKey = key
keyPair = new KeyPair(publicKey, privateKey)
} catch (Exception ex){
println(ex)
}
return keyPair;
}
public decryptData(String data, String keystorePath) throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException{
try {
byte[] dectyptedText = new byte[1]
byte[] byteArray = new byte[256]
BigInteger passwordInt = new BigInteger(data, 16)
if (passwordInt.toByteArray().length > 256) {
for (int i=1; i<257; i++) {
byteArray[i-1] = passwordInt.toByteArray()[i]
}
} else {
byteArray = passwordInt.toByteArray();
}
KeyPair generateKeyPair = getExistingKeyStoreKeyPair(keystorePath)
PrivateKey privateKey = generateKeyPair.getPrivate()
Cipher cipher = Cipher.getInstance("RSA")
cipher.init(Cipher.DECRYPT_MODE, privateKey)
dectyptedText = cipher.doFinal(byteArray)
String txt2 = new String(dectyptedText)
return txt2
}
catch (Exception ex){
println(ex)
return null
}
}
decryptData() method will playing the main role here. When you send the value data.getBytes() directly to the dycrypt method cipher.doFinal(byteArray) you get the exception - IllegalBlockSizeException size should not more than 128 bytes. So we have get rid of the issue I get the workaroud here - [Getting 1 byte extra in the modulus RSA Key and sometimes for exponents also
Basically it adds the zero when we converting data from BigInteger to byteArray. So I removed the zero from the array.
Let's start use the service class to get the key values.
String publicKey= null
String keystorePath = your file path
KeyPair generateKeyPair = encryptDecryptService.getExistingKeyStoreKeyPair(keystorePath)
PublicKey publicKey1 = generateKeyPair.getPublic()
KeyFactory keyFactory;
RSAPublicKeySpec rsaPublicKeySpec = new RSAPublicKeySpec(BigInteger.ZERO, BigInteger.ZERO)
try {
keyFactory = KeyFactory.getInstance("RSA")
rsaPublicKeySpec = keyFactory.getKeySpec(publicKey1, RSAPublicKeySpec.class)
} catch(NoSuchAlgorithmException e1) {
println(e1)
} catch(InvalidKeySpecException e) {
println(e)
}
String testPublicKey = rsaPublicKeySpec.getModulus().toString(16)
publicKey = testPublicKey
Send you publicKey to JS.
In your HTML or servlet import all the required js and jar files(you will get it from the cryptico js library).
try{
var rsa = new RSAKey();
rsa.setPublic(pub, "10001");
password = rsa.encrypt(password);
formdata = "password="+password+"&dataEncrypt=true";
}
catch (error){
console.log(error);
}
above I have directly used new RSA() instance(in cryptico library it will be different. Internaly library is using the same) and set the publickey to the instance. We have to use hex string value is '10001'. Form the query string with encrypted data which we send to server. Form data holds the encrypted data well as well as 'dataEncrypt' key value. I used to check whether data is encrypted or not.
Finally on the server side you will get the request params and below is the code for decrypt.
Boolean isDataEncrypted = false
String decryptedPassword = null
isDataEncrypted = params.containsKey("dataEncrypt")
if(params.containsKey("password")){
if(isDataEncrypted) {
String keystorePath = helperService.fetchKeystoreFilePath()
decryptedPassword = encryptDecryptService.decryptData(params.password, keystorePath)
// update decrypted data into request params
params.password = decryptedPassword
}
}
println("Data decrypted => " + decryptedPassword)

Categories