How to derive a key with JCA/JCE and with an HSM - java

I have a master key in an HSM and I want to derive it with a given diversifier.
I am quite new to JCA/JCE, and a bit lost with KeyGenerator, SecretKeyFactory, ... especially since all parameters are strings.
I want to use AES or HmacSha1.
It seems I need to use a SecretKeyFactory, and provide a KeySpecs. But which type of KeySpecs?
(I have seen a post on that topic, but I didn't seem an HSM was used.)
Thanks.

You can derive key using:
password-based derivation (PKCS#5) as described in Deriving a secret from a master key using JCE/JCA or
emulate C_Derive from PKCS#11 using encryption as described in PKCS11 deriveKey() and encrypt() returning different results for 3DES
to use HSM from JCA/JCE APIs, you need to add the corresponding provider to the JCA/JCE APIs and then specify the the provider parameter to request for that specific provider implementation.
For example:
int slot = 0;
Provider provider = new au.com.safenet.crypto.provider.SAFENETProvider(slot);
Security.addProvider(provider);
final String PROVIDER = provider.getName(); // "SAFENET", "SAFENET.1", ...
KeyGenerator keyGen = KeyGenerator.getInstance("DESede", PROVIDER);
Key baseKey = keyGen.generateKey();
Cipher desCipher = Cipher.getInstance("DESede/CBC/PKCS5Padding", PROVIDER);
desCipher.init(Cipher.ENCRYPT_MODE, baseKey);
byte[] derived = desCipher.doFinal("diversification data".getBytes());
Note that if you need to do key derivation very often, you might consider to use your provider's PCKS#11 wrapper for Java (e.g. jcprov from SafeNet) or other APIs so that you can be more explicit about its session management and be more efficient about resource usage.

Related

JWT validation using Jwts.parser giving Invalid signature

I have a token issued by Azure AD. I need to verify the token in my API, which is running on IBM platform.
I am writing the token verifier in Java,using
claims = Jwts.parser().setSigningKeyResolver(signingKeyResolver).parseClaimsJws(accessToken);
The signingKeyResolver is returning a public key. No issues.
I use the following code to get the public key :
BigInteger modulus = new BigInteger(1, Base64.getUrlDecoder().decode(key.getN()));
BigInteger exponent = new BigInteger(1, Base64.getUrlDecoder().decode(key.getE()));
RSAPublicKeySpec publicSpec = new RSAPublicKeySpec(modulus, exponent);
KeyFactory factory = KeyFactory.getInstance("RSA");
Provider provider = factory.getProvider();
PublicKey pubkey = factory.generatePublic(publicSpec);
return factory.generatePublic(publicSpec);
Can any one throw some light on Why the signature is invalid ? I observe one thing when I create the factory, the Provider name shows IBMJCEPLUS. Does it have any impact on the key generation ? If so, how do I create the correct factory for Microsoft issued keys ?
Looking for a general advice in creating the factory..
Several things.
the sample is incomplete, and seems mixed up. Is this the role of consumer doing a signature verification?
You are generating a public key which is not an expected behaviour for the verify method of a consumer, you typically 'use' the JWK retrieved from the JWKS URL provided by the JWT producer (AzureAD) not generate a new public key that was not used to generate the signature? To verify a signature, you need to reproduce the same signature by having all the inputs the server used when they generated it. A signature is simply a representation of the payload to ensure the payload was not modified, i.e. integrity check. This is the purpose of the signature and why you must have the same inputs to generate it on the client, wither you gain the inputs used out-of-band somehow (not how AzureAD works) or you use the JWKS to retrieve the appropriate public key for the verify method.
It appears you also expect there to be encrypted claims, based on the RSA reference in your example. If this is intended you would also require you to decipher the payload to access the cleartext. This requires you to have implemented the appropriate PKI. As a consumer (client) the private key must be generated and never shared (or retrieved). Encryption implemented correctly ensures that only the client intended to read the cleartext data 'can' decipher it with the private key, which it itself generated for this purpose, and only it (the client) has control of the private key. Have you the appropriate PKI to do the encrypted JWT properly? Or are you expecting to only utilise the HMAC-based JWT that has no encryption at all and relies on only signature verification (and therefore all claims are public, not protected).
To summaries, there are 2 distinct modes of JWT; 1) HMAC-based that uses signatures where the same inputs are needed on client to verify the signature as was used on server to generate the signature, and this is for integrity only, there is no encryption to protect data. 2) encryption-based JWT that uses a public key for signature verification and the private key used by clients to decipher the encrypted claims data.
1 requires PKI, 2 doesn't.
2 has no data protection and is useful for stateless claims that are intended to be public and therefore verification of the 'claims' before use is how you gain security assurance. Whereas 1 offers the security characteristic of confidentiality and if the PKI is done correctly (private keys are never shared) then the confidential claims data can have more private use cases.

Failure to decrypt RSA encrypted content(By Client) on Server

We have a client server system where client(Android phone) and server(spring ) both are using java.security.KeyFactory to get an instance of java.security.KeyFactory as shown below:
KeyFactory factory = KeyFactory.getInstance("RSA");
But if we do that, when we use this factory to encrypt data, the server gives a different output and the client gives different output. When we checked providers, it was coming to SunRsaSign for server and was OpenSSLRSA for the client. So we tried to set the same on the client using the following:
KeyFactory factory = KeyFactory.getInstance("RSA", "SunRsaSign");
But we get java.security.NoSuchProviderException error. Similarly when we try to set OpenSSLRSA on server, they also face the same error.
Complete code to encrypt is same on server and client is following:
String pubKey = "<key here>"
byte[] keyData = DatatypeConverter.parseHexBinary(pubKey);
System.out.println("key data" + Arrays.toString(keyData));
KeyFactory factory = KeyFactory.getInstance("RSA");
//System.out.println("provide = " + factory.getProvider());
PublicKey pub = factory.generatePublic(new X509EncodedKeySpec(keyData));
Cipher encryptCipher = Cipher.getInstance("RSA");
encryptCipher.init(Cipher.ENCRYPT_MODE, pub);
byte[] secretMessageBytes = msg.getBytes(StandardCharsets.UTF_8);
System.out.println("secret msg" +Arrays.toString(secretMessageBytes));
byte[] encryptedMessageBytes = encryptCipher.doFinal(secretMessageBytes);
System.out.println("enc data" +Arrays.toString(encryptedMessageBytes));
encryptedMessageBytes generated are different. Can that be a problem? I think it is because of different Providers being used by different platform.
Can somebody pls help me on how to set the provider for KeyFactory or how to remove the decryption error(javax.crypto.BadPaddingException)?
The posted code, used on both the Android and Java/Spring sides, only specifies the algorithm and not the padding when instantiating the cipher:
Cipher.getInstance("RSA")
Without explicit specification of the padding, the default padding of the providers of both sides is used. However, different providers generally define different default paddings (e.g. NoPadding, PKCS1Padding or OAEPPadding). The different paddings cause the decryption to fail, because a prerequisite for successful decryption is that the same padding is used as for encryption.
To avoid such things, the padding should always be specified when instantiating the cipher, especially in a cross-platform environment, e.g.:
Cipher.getInstance("RSA/ECB/PKCS1Padding")
On the left is the algorithm and on the right the padding. Note that the middle part (ECB) has no meaning for RSA (it is an artifact of using the scheme of symmetric encryption specifying the operation mode in the middle, which is not defined for asymmetric encryption).
Fixing the issue with the explicit specification of the padding proves that the padding was indeed the problem.
I can only speculate about the default paddings used in your environment.
I could not test the OpenSSLRSA provider, as it is not available in my environment. On my machine Android (API Level 28, P) applies the AndroidOpenSSL provider (aka Conscrypt). This defaults to NoPadding, while Java's SunJCE provider defaults to PKCS1Padding. Encryption on Android and decryption on Java would result in an exception (or non-removed padding for the other direction).
You can determine the default padding of your environment as follows: Encrypt with the default padding (by specifying only the algorithm) and vary the padding on decryption until decryption is successful and the original plaintext is decrypted.

Why does Lambda generate identical cryptographic keys after initialization and how to fix it?

I've noticed something weird when generating key pairs from AWS Lambda - every time I run the code it generates identical keys. I am aware that Lambda containers are frozen after each invocation and this is probably why the underlying JCE classes are loaded from memory and keep their initial state. The code in question is relatively simple:
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA", "BC");
keyPairGen.initialize(2048);
KeyPair keyPair = keyPairGen.generateKeyPair();
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
return new RSAKey.Builder(rsaPublicKey).privateKey(rsaPrivateKey)
.keyID(kid).keyUse(KeyUse.SIGNATURE)
I tried both the vanilla provider and Bouncy Castle but the result is the same - identical key pairs when Lambda is "warm". Once the container is terminated and restarted from a "cold" state, I get a new and different set of keys.
I'm also using AWS Cognito and the service is served through both API Gateway and CloudFront.
Any ideas how to "refresh" the underlying JCE classes?
To answer my own question, the culprit was actually CloudFront. Even with API caching turned off in API Gateway, CloudFront still caches the responses for some API requests.
If someone comes across the same problem, the solution is to "bust" the CloudFront cache by appending a query parameter to the request URL:
GET /api/generateKeyPair?timestamp=1507843759370

How to sign and validate RSA PKCS#1 v2.0 signatures in Java

I'm trying to sign and validate signature in RSA+SHA1 with PKCS1 v2.0 padding.
I haven't found in the documentation (JDK or JCE) which algorithm/padding I have to use.
It seems that I've to use OAEP padding but I didn't succeed:
For validation, I've tried via Cipher in DECRYPT_MODE with the public key sent by the signer:
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWITHSHA1ANDMGF1PADDING", "SunJCE");
cipher.init(Cipher.DECRYPT_MODE, pub); //exception !
But I got an exception:
java.security.InvalidKeyException: OAEP cannot be used to sign or verify signatures
at com.sun.crypto.provider.RSACipher.init(RSACipher.java:303)
at com.sun.crypto.provider.RSACipher.engineInit(RSACipher.java:207)
...
(For unknown reason, OAEPWITHSHA1ANDMGF1PADDING is only accepted with ENCRYPT_MODE+PUBLIC KEY or DECRYPT_MODE+PRIVATE KEY, the opposite that I want to do....)
For signature, I'm trying to use Signature but I don't know which algorithm to use:
Signature mySig2 = Signature.getInstance("SHA1withRSAandMGF1");
Doesn't work :
java.security.NoSuchAlgorithmException: SHA1withRSAandMGF1 Signature not available
at java.security.Signature.getInstance(Signature.java:229)
...
Can somebody help me?
Hope I'm not too late to respond to this. Very recently I had to face the same issue with RSA encryption. My Signer wants to do the verification to the data that is sent through POST. This is how I managed to work on that in Java.
Step 1 - Add following into your project
bcprov-ext-jdk14-1.45.jar
commons-codec-1.4.jar
Step 2 - Declare your Cipher
private Cipher cipher;
Step 3 - Call below in your class constructor
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
this.cipher = Cipher.getInstance("RSA/NONE/OAEPWithSHA1AndMGF1Padding");
Step 4 - Get your PublicKey from public key text string
public PublicKey getPublic(String pubKey) throws Exception {
byte[] publicBytes = Base64.decodeBase64(pubKey);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(keySpec);
}
Step 5 - Verification method
public boolean verfyRSA(String signature, String data, PublicKey key) throws Exception {
byte[] sign = Base64.decodeBase64(signature);
byte[] dat = Base64.decodeBase64(data);
Signature rsaVerify = Signature.getInstance("SHA1withRSAandMGF1");
rsaVerify.initVerify(key);
rsaVerify.update(dat);
return rsaVerify.verify(sign);
}
Step 6 - Usage
Yourclass yc = new YourClass();
PublicKey publicKey = yc.getPublic("Your PublicKey without leading and trailing -------text------");
boolean b = yc.verfyRSA("POST_Signature", "POST_data", publicKey);
Hope this helps. Mind the getInstance() parameters. These are the ones that do the trick. Cheers
I'm trying to sign and validate signature in RSA+SHA1 with PKCS1 v2.0 padding.
This is a strange requirement.
First of all, RSA is secure even if PKCS#1 v1.5 compatible padding is used. It is however not secure when SHA-1 is used to hash the input/message, for the simple reasons that SHA-1 is not considered secure anymore for that kind of purpose.
Second, although PKCS#1 v2.0 contains a new padding mode for signature generation called PSS or - to be more precise - RSASSA-PSS it also contains the single signature generation mode from 1.5 of the standard called RSASSA-PKCS1-v1_5. So at least in theory your requirements are not clear.
Now PKCS#1 v1.5 padding for encryption is known to be broken against padding oracle attacks. For that reason alone, OAEP encryption introduced in PKCS#1 v2.0 is generally available.
PSS for signature generation has a better security proof, but PKCS#1 v1.5 is still considered secure. This is likely why Oracle / SUN didn't include PSS within Java SE JCA. If you want it you can vote for it here.
I haven't found in the documentation (JDK or JCE) which algorithm/padding I have to use. It seems that I've to use OAEP padding but I didn't succeed.
OAEP stands for Optimal Asymmetric Encryption Padding. Padding for encryption cannot/should not be used for signature generation.
After adding the Bouncy Castle provider the RSA PSS becomes available. You can use your (insecure) scheme with "SHA1withRSA/PSS" or indeed the identical "SHA1withRSAandMGF1". It's likely that Oracle's Java SE will support RSA/PSS once the TLS 1.3 specification is finalized.

what is deference between "RSA/ECB/OAEPWITHSHA256ANDMGF1PADDING" and "RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING"

As per my knowledge both are same but one is working on one PC while same code says:
javax.crypto.NoSuchPaddingException: OAEPWITHSHA-256ANDMGF1PADDING unavailable with RSA on another machine.
When I am removing dash - from the name (OAEPWITHSHA256ANDMGF1PADDING) it starts running on another machine but leads error to some other line bad padding exception.
What could be the reason?
Sample code for Hint
I am using jdk1.7.0_71 32bit:
private byte[] decryptSecretKeyData(byte[] encryptedSecretKey, byte[] iv, PrivateKey privateKey) throws Exception
{
try {
Provider provider= new sun.security.pkcs11.SunPKCS11(keyStoreFile1);
Security.addProvider(provider);
LOG.info("**************Inside decryptSecretKeyData***********************");
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING", provider);
// decrypting the session key with rsa no padding.
rsaCipher.init(Cipher.DECRYPT_MODE, privateKey);
/* The reason is RSA OAEP SHA256 is not supported in HSM. */
byte[] decKey = rsaCipher.doFinal(encryptedSecretKey);
OAEPEncoding encode = new OAEPEncoding(new RSAEngine(), new SHA256Digest(), iv);
LOG.info("******************RSAPublicKey rsaPublickey = (*****************************");
java.security.interfaces.RSAPublicKey rsaPublickey = (java.security.interfaces.RSAPublicKey) publicKeyFile;
RSAKeyParameters keyParams = new RSAKeyParameters(false, rsaPublickey.getModulus(), EXPONENT);
encode.init(false, keyParams);
LOG.info("******************encode.processBlock(decKey, 0, decKey.length);************************");
byte decryptedSecKey[] = encode.processBlock(decKey, 0, decKey.length);
return decryptedSecKey;
} catch (InvalidCipherTextException e) {
LOG.info("*******************Failed to decrypt AES secret key using RSA :**********************");
throw new Exception("Failed to decrypt AES secret key using RSA :" + e.toString());
}
}
RSA/ECB/OAEPWITHSHA256ANDMGF1PADDING and RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING are different alias but both refers to the same algorithms, so in this sense there is not difference at all.
The thing is that you're using a PKCS#11 (cryptographic token interface) to cipher your data. According to java PKCS#11 reference:
The Sun PKCS#11 provider, in contrast to most other providers, does not implement cryptographic algorithms itself.
Instead, it acts as a bridge between the Java JCA and JCE APIs and the
native PKCS#11 cryptographic API, translating the calls and
conventions between the two. This means that Java applications calling
standard JCA and JCE APIs can, without modification, take advantage of
algorithms offered by the underlying PKCS#11 implementations, such as,
for example,
Cryptographic Smartcards, Hardware cryptographic accelerators, and
High performance software implementations. Note that Java SE only
facilitates accessing native PKCS#11 implementations, it does not
itself include a native PKCS#11 implementation. However, cryptographic
devices such as Smartcards and hardware accelerators often come with
software that includes a PKCS#11 implementation, which you need to
install and configure according to manufacturer's instructions.
Summarized, if you're using a PKCS#11, the use of the algorithms depends on vendors native implementation (.dll on windows, .so on linux ...) and some times on specific program connector so: check if in the both PCs you're using the same drivers/program version for your PKCS#11 token and the both are correctly installed because probably there is an error in one of them which doesn't allows you to use RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING correctly.
Hope this helps,
Both algorithms are provided by the different security provider. The
RSA/ECB/OAEPWITHSHA256ANDMGF1PADDING
is provided by the Bouncy Castle provider while
RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING
is provided by the SUN JCE provider. In our case, we are able to successfully use Bouncy Castle provider algorithm but if I replace that with SUN JCE algorithm then it gives following error:
Exception in thread "main" javax.crypto.BadPaddingException: lHash mismatch
at sun.security.rsa.RSAPadding.unpadOAEP(RSAPadding.java:425)
at sun.security.rsa.RSAPadding.unpad(RSAPadding.java:274)
at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:356)
at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:382)
The situation is more complex.
The RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING is nominally provided by SunJCE.
The RSA/NONE/OAEPWITHSHA256ANDMGF1PADDING is provided by BC.
However BC is able to take over and act as a provider of RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING if it is installed before the SunJCE provider (using Security.installProviderAt). Unfortunately, BC does not parametrize this algorithm the same way as SunJCE does it. BC implementation of that algorithm is thus different (!) than one provided by SunJCE, which will be source of padding errors.
SunJCE RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING is using SHA-1 for MGF1 function and SHA-256 for label hash (which is almost always an empty byte array and therefore a static value).
BC RSA/NONE/OAEPWITHSHA256ANDMGF1PADDING is using SHA-256 for MGF1 function and SHA-256 for label hash. BC will also use the same parameters if it is being asked to provide RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING.
Advice: always explicitly specify security provider to make sure that correct provider is used. Alternatively explicitly specify the OAEP parameters and don't rely on defaults (recommended).
I've reported the issue to BC, so maybe it will get fixed.

Categories

Resources