PropertyPlaceholderConfigurer and PropertySourcesPlaceholderConfigurer run differently - java

I used the DES algorithm to encrypt the username and password in jdbc.properties, and used the PropertyPlaceholderConfigurer for decryption, but found that this class has been deprecated. So use PropertySourcesPlaceholderConfigurer to replace.
The bean has been added in spring-dao.xml, and the class is filled with the class containing the decryption method inherited from PropertySourcesPlaceholderConfigurer.
I put a breakpoint on the first line of the decryption method, and then started tomcat to send an access request. At this point, the backend should call the database. But if the decryption class inherits from PropertySourcesPlaceholderConfigurer, the first line of the decryption method will not be executed. If the decryption class inherits from PropertyPlaceholderConfigurer, the first line of the decryption method is executed. I don't know why this is the case, should I use the deprecated PropertyPlaceholderConfigurer?
spring.version: 5.2.0.RELEASE
Part of spring-dao.xml
<!--<context:property-placeholder location="classpath:jdbc.properties" />-->
<bean class="com.imooc.o2o.util.EncryptPropertySourcesPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:jdbc.properties</value>
</list>
</property>
<property name="fileEncoding" value="UTF-8" />
</bean>
EncryptPropertySourcesPlaceholderConfigurer.java (My decryption algorithm class)
package com.imooc.o2o.util;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
public class EncryptPropertySourcesPlaceholderConfigurer extends PropertySourcesPlaceholderConfigurer {
// Fields to be encrypted
private String[] encryptPropNames = {"jdbc.username", "jdbc.password"};
/**
* Transform key attributes
* #param propertyName
* #param propertyValue
* #return
*/
#Override
protected String convertProperty(String propertyName, String propertyValue) {
if (isEncryptProp(propertyName)) {
// Decrypting encrypted fields
String decryptValue = DESUtil.getDecryptString(propertyValue);
return decryptValue;
} else {
return propertyValue;
}
}
/**
* Whether the attribute is encrypted
* #param propertyName
* #return
*/
private boolean isEncryptProp(String propertyName) {
// If it is equal to the field to be encrypted, it has been encrypted
for (String encryptPropertyName : encryptPropNames) {
if (encryptPropertyName.equals(propertyName)) {
return true;
}
}
return false;
}
}
DESUtil.java
package com.imooc.o2o.util;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import java.security.Key;
import java.security.SecureRandom;
import java.util.Base64;
/**
* DES is a symmetric encryption algorithm. The so-called symmetric encryption algorithm is an algorithm that uses the same key for encryption and decryption.
*/
public class DESUtil {
private static Key key;
private static String KEY_STR = "myKey";
private static String CHAR_SET_NAME = "UTF-8";
private static String ALGORITHM = "DES";
static {
try {
// Generate DES Algorithm Object
KeyGenerator generator = KeyGenerator.getInstance(ALGORITHM);
// Apply SHA1 security policy
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
// Setting the key seed
secureRandom.setSeed(KEY_STR.getBytes());
// Initialize SHA1-based algorithm objects
generator.init(secureRandom);
// Generate a key object
key = generator.generateKey();
generator = null;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// Get encrypted information
public static String getEncryptString(String str) {
// Based on BASE64 encoding, receive byte [] and convert to String
Base64.Encoder encoder = Base64.getEncoder();
try {
// Encoded as UTF-8
byte[] bytes = str.getBytes(CHAR_SET_NAME);
// Get the encrypted object
Cipher cipher = Cipher.getInstance(ALGORITHM);
// Initialize password information
cipher.init(Cipher.ENCRYPT_MODE, key);
// encryption
byte[] doFinal = cipher.doFinal(bytes);
// byte[] to encode a good String and return
return encoder.encodeToString(doFinal);
} catch (Exception e) {
throw new RuntimeException();
}
}
// Get the decrypted information
public static String getDecryptString(String str) {
// Based on BASE64 encoding, receive byte[] and convert to String
Base64.Decoder decoder = Base64.getDecoder();
try {
// Decode string into byte []
byte[] bytes = decoder.decode(str);
// Get the decrypted object
Cipher cipher = Cipher.getInstance(ALGORITHM);
// Initialize decryption information
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] doFinal = cipher.doFinal(bytes);
// Returns the decrypted information
return new String(doFinal, CHAR_SET_NAME);
} catch (Exception e) {
throw new RuntimeException();
}
}
/**
* Get the string to be encrypted
* #param args
*/
public static void main(String[] args) {
System.out.println(getEncryptString(""));
System.out.println(getEncryptString(""));
}
}

Answer my own question, maybe because I did not use Spring Boot.
The implementation logic of these two classes is different. Spring Boot use PropertySourcesPlaceholderConfigurer.
This answer is not accurate. I am a beginner and I have not figured it out in many places. Please understand if the deviation is large.

Related

The signing key's size is 48 bits which is not secure enough for the HS256 algorithm

I'm this is my first time making a project that needs JWT authentication, I'm following this tutorial, but I get the problem that my key must be higher than 256bits, I searched here and found this post, however I'm new in this topic so I don't really know how to make it 256bits. Below is my code:
#Service
public class JwtGeneration implements IJwtGeneration {
#Value("${jwt.secret}")
private String secret;
#Value("${app.jwttoken.message}")
private String message;
#Override
public Map<String, String> generateToken(User user) {
String jwtToken="";
jwtToken = Jwts.builder().setSubject(user.getUserName()).setIssuedAt(new Date()).signWith(SignatureAlgorithm.HS256, "secret").compact();
Map<String, String> jwtTokenGen = new HashMap<>();
jwtTokenGen.put("token", jwtToken);
jwtTokenGen.put("message", message);
return jwtTokenGen;
}
}
How can I make it 256 bits?
Searching more, I found that I could try this:
#Service
public class JwtGeneration implements IJwtGeneration {
#Value("${jwt.secret}")
private String secret;
byte[] decodedKey = secret.getBytes(StandardCharsets.UTF_8);
SecretKey key = new SecretKeySpec(decodedKey, 0, decodedKey.length, "HMACSHA256");
#Value("${app.jwttoken.message}")
private String message;
#Override
public Map<String, String> generateToken(User user) {
String jwtToken="";
jwtToken = Jwts.builder().setSubject(user.getUserName()).setIssuedAt(new Date()).signWith(key, SignatureAlgorithm.HS256).compact();
Map<String, String> jwtTokenGen = new HashMap<>();
jwtTokenGen.put("token", jwtToken);
jwtTokenGen.put("message", message);
return jwtTokenGen;
}
}
But I still get error, how can I fix it?
UPDATE
This is the error message I get: "io.jsonwebtoken.security.WeakKeyException: The signing key's size is 48 bits which is not secure enough for the HS256 algorithm. The JWT JWA Specification (RFC 7518, Section 3.2) states that keys used with HS256 MUST have a size >= 256 bits (the key size must be greater than or equal to the hash output size). Consider using the io.jsonwebtoken.security.Keys class's 'secretKeyFor(SignatureAlgorithm.HS256)' method to create a key guaranteed to be secure enough for HS256. See https://tools.ietf.org/html/rfc7518#section-3.2 for more information.
at com.authdemo.AuthDemo.config.JwtGeneration.generateToken(JwtGeneration.java:26) ~[classes/:na]
at com.authdemo.AuthDemo.controller.UserController.loginUser(UserController.java:44) ~[classes/:na]"
I tried SecretKey key = secretKeyFor(SignatureAlgorithm.HS256) but it didn't work.
I found the solution, I had to add to my pom the following dependencies: jjwt-impl and jjwt-jackson. Also I had to create my secret key as follows:
private String secret = "2D4A614E645267556B58703273357638792F423F4428472B4B6250655368566D";
#Value("${app.jwttoken.message}")
private String message;
#Override
public Map<String, String> generateToken(User user) {
String jwtToken="";
jwtToken = Jwts.builder().setSubject(user.getUserName()).setIssuedAt(new Date()).signWith(getSignInKey(),SignatureAlgorithm.HS256).compact();
Map<String, String> jwtTokenGen = new HashMap<>();
jwtTokenGen.put("token", jwtToken);
jwtTokenGen.put("message", message);
return jwtTokenGen;
}
private Key getSignInKey() {
byte[] keyBytes = Decoders.BASE64.decode(secret);
return Keys.hmacShaKeyFor(keyBytes);
}
Turns out I was using "secretkey" as my jwt.secret in application properties. And I was passing that variable alone. What I did now was use an online keygenerator and then created a function to convert it to a Key and then passed it to signWith() method.

unsafe cryptographic encryption patterns , How to solve it? [duplicate]

This question already has answers here:
"Your app contains unsafe cryptographic encryption patterns" - How I can get rid of this warning?
(2 answers)
Closed 3 years ago.
I'm encrypting the password for firebase sign in, it's working well but I received a warning in google play console that your app contains unsafe cryptographic encryption patterns how can I get rid of it ??
I'm trying it on android studio.
public static class AESCrypt
{
private static final String ALGORITHM = "AES";
private static final String KEY = "1Hbfh667adfDEJ78";
public static String encrypt(String value) throws Exception
{
Key key = generateKey();
Cipher cipher = Cipher.getInstance(AESCrypt.ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte [] encryptedByteValue = cipher.doFinal(value.getBytes("utf-8"));
String encryptedValue64 = Base64.encodeToString(encryptedByteValue, Base64.DEFAULT);
return encryptedValue64;
}
public static String decrypt(String value) throws Exception
{
Key key = generateKey();
Cipher cipher = Cipher.getInstance(AESCrypt.ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decryptedValue64 = Base64.decode(value, Base64.DEFAULT);
byte [] decryptedByteValue = cipher.doFinal(decryptedValue64);
String decryptedValue = new String(decryptedByteValue,"utf-8");
return decryptedValue;
}
private static Key generateKey() throws Exception
{
Key key = new SecretKeySpec(AESCrypt.KEY.getBytes(),AESCrypt.ALGORITHM);
return key;
}
The main issues are that you use a cipher with no integrity and a hard coded cryptographic key. If you analyse your source with Find Security Bugs you get CIPHER_INTEGRITY and HARD_CODE_KEY warning:
The cipher does not provide data integrity [com.lloyds.keystorage.AESCrypt] At AESCrypt.java:[line 25] CIPHER_INTEGRITY
The cipher does not provide data integrity [com.lloyds.keystorage.AESCrypt] At AESCrypt.java:[line 15] CIPHER_INTEGRITY
Hard coded cryptographic key found [com.lloyds.keystorage.AESCrypt] At AESCrypt.java:[line 35] HARD_CODE_KEY
The solution is to use a cipher that includes a Hash based Message Authentication Code (HMAC) to sign the data:
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
And to store the secret key in separate configuration files or keystores.
Below is the whole class after a full refactoring:
import android.util.Base64
import static java.nio.charset.StandardCharsets.UTF_8;
import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public class AESCrypt {
private static final String TRANSFORMATION = "AES/GCM/NoPadding";
public static String encrypt(String value) throws Exception {
Key key = generateKey();
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encryptedByteValue = cipher.doFinal(value.getBytes(UTF_8));
return Base64.encodeToString(encryptedByteValue, Base64.DEFAULT);
}
public static String decrypt(String value) throws Exception {
Key key = generateKey();
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decryptedValue64 = Base64.decode(value, Base64.DEFAULT);
byte[] decryptedByteValue = cipher.doFinal(decryptedValue64);
return new String(decryptedByteValue, UTF_8);
}
private static Key generateKey() {
return new SecretKeySpec(Configuration.getKey().getBytes(UTF_8), TRANSFORMATION);
}
}

How do I decode a JWT token using an RSA public key in PEM format?

My Java application is receiving a JWT. I have the public key in PEM format:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAixn0CGu8/M4txn4pdp8K
m8RQfVa+cHX25/a5sPmzP49u7YlQsRvtOexzgdwDcfUJm3hHMZcbZBtrHKsS8q4Q
QtGQioyVml8EaLuFNFYisaIEldVyRbXFG54FNp03vSU9ImS/cOiM9swo+1w5JgWO
F9efy7JO40LA9E7lv64COUYjFhrn+HRZuKoblL19+Sj49FyXexAUS29UM9PfIdY6
ar1FA8cxzPqW7EkXZ0Mua3IzNnYcjMvUL9TJwoLAAz9S1Tv4Is5jupy9UXkuJ4r8
Jx9DqI3Q3ur0VekYSd5tnTI4K+no9ABCFVv7+6Q45Ec2eB0xMwlqI+phcGhGMVCX
1QIDAQAB
-----END PUBLIC KEY-----
I know I can use a JwtConsumer to verify and decode the JWT:
JwtConsumer jwtConsumer = new JwtConsumerBuilder()
.setRequireExpirationTime()
.setVerificationKey(publicKey) // what do I pass here?
.build();
But how do I convert my PEM file into a format understood by .setVerificationKey()?
I had to first Base64 decode the public key to its constituent bytes, then encode the bytes in X509 format.
Here is a complete example showing this, and how to read some claims from the decoded JWT:
package jwt;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import sun.misc.BASE64Decoder;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Map;
public class JwtExample {
public void decodeJwt(String jwt) throws Exception {
// read public key from a file or config or something
String publicKeyPEM =
"-----BEGIN PUBLIC KEY-----\n" +
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAixn0CGu8/M4txn4pdp8K\n" +
"m8RQfVa+cHX25/a5sPmzP49u7YlQsRvtOexzgdwDcfUJm3hHMZcbZBtrHKsS8q4Q\n" +
"QtGQioyVml8EaLuFNFYisaIEldVyRbXFG54FNp03vSU9ImS/cOiM9swo+1w5JgWO\n" +
"F9efy7JO40LA9E7lv64COUYjFhrn+HRZuKoblL19+Sj49FyXexAUS29UM9PfIdY6\n" +
"ar1FA8cxzPqW7EkXZ0Mua3IzNnYcjMvUL9TJwoLAAz9S1Tv4Is5jupy9UXkuJ4r8\n" +
"Jx9DqI3Q3ur0VekYSd5tnTI4K+no9ABCFVv7+6Q45Ec2eB0xMwlqI+phcGhGMVCX\n" +
"1QIDAQAB\n" +
"-----END PUBLIC KEY-----";
// decode to its constituent bytes
publicKeyPEM = publicKeyPEM.replace("-----BEGIN PUBLIC KEY-----\n", "");
publicKeyPEM = publicKeyPEM.replace("-----END PUBLIC KEY-----", "");
BASE64Decoder base64Decoder = new BASE64Decoder();
byte[] publicKeyBytes = base64Decoder.decodeBuffer(publicKeyPEM);
// create a key object from the bytes
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(keySpec);
// create a JWT consumer
JwtConsumer jwtConsumer = new JwtConsumerBuilder()
.setRequireExpirationTime()
.setVerificationKey(publicKey)
.build();
// validate and decode the jwt
// eg: jwt = "eyJhbGciOiJSUzI1NiJ9.eyJ1c2VybmFtZSI6Ik1DaGFtYmU0IiwiZXhwIjoxNDU2OTEwODgzLCJzY29wZSI6WyJvcGVuaWQiLCJwMnAiLCJociIsImRhcyIsIm1lIl0sImNsaWVudF9pZCI6Im1vYmlsZSIsImp0aSI6ImNZcHBMYXltVzlmNXFBZk4ifQ.QqZI9vV8IznTjN-GtUSCri9-6HH6Yl1Oae6K8-d2yjQ4fysF5d3wStdL2kMazl7xeqbtSIsw-F5Aol9eHdGAu54b9IyBEM_QIasy0lnT8xFk0Zi36NJ-7yhl_89f6SB6TGimM59xUvzXxuAw3FzWM6TbiptInrCL2TXkhS69Gng-ANPeiSITUX5A1TDInssds6ZoSb7IOUMtxPGfrbO9sBjx8aJlIu9igkqk4OX5xBmxLp3icoo98I5v9Wt_Huu7eWKBfOskMSEav4X_m5_phbAZJ_F8nWRmcxk6O7hCQdawzegnhMxP2IPIhwlWRNX_8WxkNErq2fJgdazDf8pS_Q";
JwtClaims jwtDecoded = jwtConsumer.processToClaims(jwt);
Map<String, Object> jwtClaims = jwtDecoded.getClaimsMap();
String username = (String) jwtClaims.get("username"); // "MChambe4"
// ensure the required scope is claimed
String requiredScope = "das";
ArrayList scopes = (ArrayList) jwtClaims.get("scope");
// ensure claims contains the required scope
if (!scopes.stream().anyMatch(scope -> scope == requiredScope)) {
throw new Exception("Required scope is not claimed: " + requiredScope);
}
}
}
With v0.5.0 there is some utility support for dealing with the PEM encoded public keys, RsaKeyUtil.fromPemEncoded(String pem), which can maybe simplify things for you a bit. You can also get claim values directly from the JwtClaims object, which might also simplify. Here's your example with those slight modifications:
String jwt = "eyJhbGciOiJSUzI1NiJ9.eyJ1c2VybmFtZSI6Ik1DaGFtYmU0IiwiZXhwIjoxNDU2OTEwODgzLCJzY29wZSI6WyJvcGVuaWQiLCJwMnAiLCJociIsImRhcyIsIm1lIl0sImNsaWVudF9pZCI6Im1vYmlsZSIsImp0aSI6ImNZcHBMYXltVzlmNXFBZk4ifQ.QqZI9vV8IznTjN-GtUSCri9-6HH6Yl1Oae6K8-d2yjQ4fysF5d3wStdL2kMazl7xeqbtSIsw-F5Aol9eHdGAu54b9IyBEM_QIasy0lnT8xFk0Zi36NJ-7yhl_89f6SB6TGimM59xUvzXxuAw3FzWM6TbiptInrCL2TXkhS69Gng-ANPeiSITUX5A1TDInssds6ZoSb7IOUMtxPGfrbO9sBjx8aJlIu9igkqk4OX5xBmxLp3icoo98I5v9Wt_Huu7eWKBfOskMSEav4X_m5_phbAZJ_F8nWRmcxk6O7hCQdawzegnhMxP2IPIhwlWRNX_8WxkNErq2fJgdazDf8pS_Q";
// read public key from a file or config or something
String publicKeyPEM =
"-----BEGIN PUBLIC KEY-----\n" +
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAixn0CGu8/M4txn4pdp8K\n" +
"m8RQfVa+cHX25/a5sPmzP49u7YlQsRvtOexzgdwDcfUJm3hHMZcbZBtrHKsS8q4Q\n" +
"QtGQioyVml8EaLuFNFYisaIEldVyRbXFG54FNp03vSU9ImS/cOiM9swo+1w5JgWO\n" +
"F9efy7JO40LA9E7lv64COUYjFhrn+HRZuKoblL19+Sj49FyXexAUS29UM9PfIdY6\n" +
"ar1FA8cxzPqW7EkXZ0Mua3IzNnYcjMvUL9TJwoLAAz9S1Tv4Is5jupy9UXkuJ4r8\n" +
"Jx9DqI3Q3ur0VekYSd5tnTI4K+no9ABCFVv7+6Q45Ec2eB0xMwlqI+phcGhGMVCX\n" +
"1QIDAQAB\n" +
"-----END PUBLIC KEY-----";
RsaKeyUtil rsaKeyUtil = new RsaKeyUtil();
PublicKey publicKey = rsaKeyUtil.fromPemEncoded(publicKeyPEM);
// create a JWT consumer
JwtConsumer jwtConsumer = new JwtConsumerBuilder()
.setRequireExpirationTime()
.setVerificationKey(publicKey)
.build();
// validate and decode the jwt
JwtClaims jwtDecoded = jwtConsumer.processToClaims(jwt);
String username = jwtDecoded.getStringClaimValue("username"); // "MChambe4"
// ensure the required scope is claimed
String requiredScope = "das";
List<String> scopes = jwtDecoded.getStringListClaimValue("scope");
if (!scopes.stream().anyMatch(scope -> scope.equals(requiredScope))) {
throw new Exception("Required scope is not claimed: " + requiredScope);
}
}

Decrypt with jasypt after retrieving the encrypted value in Http Request

I am having problem to make work jaspyt in this scenario:
StrongTextEncryptor textEncryptor = new StrongTextEncryptor();
textEncryptor.setPassword("myPassword");
String myEncryptedParam = textEncryptor.encrypt("myClearMessage");
myObject.setCallbackUrl("http://myhost/notification?myparam="+myEncryptedParam);
When I receive the callback url and try to decrypt the param 'myParam' provided in the url WITH THE SAME STRONGTEXTENCRYPTOR used in the request, it raises an exception:
org.jasypt.exceptions.EncryptionOperationNotPossibleException
at org.jasypt.encryption.pbe.StandardPBEByteEncryptor.decrypt(StandardPBEByteEncryptor.java:1055)
at org.jasypt.encryption.pbe.StandardPBEStringEncryptor.decrypt(StandardPBEStringEncryptor.java:725)
at org.jasypt.util.text.StrongTextEncryptor.decrypt(StrongTextEncryptor.java:118)
at com.softlysoftware.caligraph.util.Util.decryptMessage(Util.java:30)
Digging a bit more in the exception I get:
BadPaddingException: Given final block not properly padded
If I test the encryption/decryption process without httprequest, works ok.
The problem is that StrongTextEncryptor uses StandardPBEStringEncryptor which in turn uses Base64 to encode the ciphertexts. The problem is that Base64 has a / character which is not URL-safe. When you try to decrypt, the parameter parser that you use probably drops those / characters which makes the ciphertext incomplete.
The easiest solution is probably to change the offending characters with replace all:
myEncryptedParam.replaceAll("/", "_").replaceAll("\\+", "-");
and back again before you try to decrypt:
receivedParam.replaceAll("_", "/").replaceAll("-", "\\+");
This transforms the encoding from the normal Base64 encoding to the "URL and Filename safe" Base 64 alphabet.
Building on Artjom's answer, here is a Jasypt text encryptor wrapper
import org.jasypt.util.text.TextEncryptor;
public class UrlSafeTextEncryptor implements TextEncryptor {
private TextEncryptor textEncryptor; // thread safe
public UrlSafeTextEncryptor(TextEncryptor textEncryptor) {
this.textEncryptor = textEncryptor;
}
public String encrypt(String string) {
String encrypted = textEncryptor.encrypt(string);
return encrypted.replaceAll("/", "_").replaceAll("\\+", "-");
}
public String decrypt(String encrypted) {
encrypted = encrypted.replaceAll("_", "/").replaceAll("-", "\\+");
return textEncryptor.decrypt(encrypted);
}
}
and corresponding test case
import org.jasypt.util.text.StrongTextEncryptor;
import org.jasypt.util.text.TextEncryptor;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class UrlSafeTextEncryptorTest {
private String password = "12345678";
protected TextEncryptor encryptor;
protected UrlSafeTextEncryptor urlSafeEncryptor;
#Before
public void init() {
StrongTextEncryptor encryptor = new StrongTextEncryptor(); // your implementation here
encryptor.setPassword(password);
this.encryptor = encryptor;
this.urlSafeEncryptor = new UrlSafeTextEncryptor(encryptor);
}
#Test
public void scramble_roundtrip_urlSafe() {
int i = 0;
while(true) {
String key = Integer.toString(i);
String urlSafeEncrypted = urlSafeEncryptor.encrypt(key);
Assert.assertFalse(urlSafeEncrypted, urlSafeEncrypted.contains("/"));
Assert.assertEquals(key, urlSafeEncryptor.decrypt(urlSafeEncrypted));
if(urlSafeEncrypted.contains("_")) {
break;
}
i++;
}
}
}

OpenID Connect - how to verify id token in Java?

I've implemented the basic OpenID connect flow in my java application and it seems to work fine.
I'd like to use an existing java library to verify the id token, as detailed here on a Salesforce page about implementing OpenId connect.
Are there any existing libraries that implement this well? I've got the response parsed, I just need to find some simple way to verify the id token is valid.
The following example will validate an id_token from an OAuth2 call for Salesforce, without any 3rd party libraries. Note that you'll have to supply a valid id_token below to test this out.
package jwt_validate_signature_sf_no_third_party;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.RSAPublicKeySpec;
import org.apache.commons.codec.binary.Base64;
public class Main
{
// Sample id_token that needs validation. This is probably the only field you need to change to test your id_token.
// If it doesn't work, try making sure the MODULUS and EXPONENT constants are what you're using, as detailed below.
public static final String id_token = "YOUR_ID_TOKEN_HERE";
public static final String[] id_token_parts = id_token.split("\\.");
// Constants that come from the keys your token was signed with.
// Correct values can be found from using the "kid" value and looking up the "n (MODULUS)" and "e (EXPONENT)" fields
// at the following url: https://login.salesforce.com/id/keys
// MAJOR NOTE: This url will work for 90% of your use cases, but for the other 10%
// you'll need to make sure you get the "kid" value from the instance url that
// the api responses from Salesforce suggest for your token, as the kid values *will* be different.
// e.g. Some users would need to get their kid values from https://na44.salesforce.com/id/keys for example.
// The following 2 values are hard coded to work with the "kid=196" key values.
public static final String MODULUS = "5SGw1jcqyFYEZaf39RoxAhlq-hfRSOsneVtsT2k09yEQhwB2myvf3ckVAwFyBF6y0Hr1psvu1FlPzKQ9YfcQkfge4e7eeQ7uaez9mMQ8RpyAFZprq1iFCix4XQw-jKW47LAevr9w1ttZY932gFrGJ4gkf_uqutUny82vupVUETpQ6HDmIL958SxYb_-d436zi5LMlHnTxcR5TWIQGGxip-CrD7vOA3hrssYLhNGQdwVYtwI768EvwE8h4VJDgIrovoHPH1ofDQk8-oG20eEmZeWugI1K3z33fZJS-E_2p_OiDVr0EmgFMTvPTnQ75h_9vyF1qhzikJpN9P8KcEm8oGu7KJGIn8ggUY0ftqKG2KcWTaKiirFFYQ981PhLHryH18eOIxMpoh9pRXf2y7DfNTyid99ig0GUH-lzAlbKY0EV2sIuvEsIoo6G8YT2uI72xzl7sCcp41FS7oFwbUyHp_uHGiTZgN7g-18nm2TFmQ_wGB1xCwJMFzjIXq1PwEjmg3W5NBuMLSbG-aDwjeNrcD_4vfB6yg548GztQO2MpV_BuxtrZDJQm-xhJXdm4FfrJzWdwX_JN9qfsP0YU1_mxtSU_m6EKgmwFdE3Yh1WM0-kRRSk3gmNvXpiKeVduzm8I5_Jl7kwLgBw24QUVaLZn8jC2xWRk_jcBNFFLQgOf9U";
public static final String EXPONENT = "AQAB";
public static final String ID_TOKEN_HEADER = base64UrlDecode(id_token_parts[0]);
public static final String ID_TOKEN_PAYLOAD = base64UrlDecode(id_token_parts[1]);
public static final byte[] ID_TOKEN_SIGNATURE = base64UrlDecodeToBytes(id_token_parts[2]);
public static String base64UrlDecode(String input)
{
byte[] decodedBytes = base64UrlDecodeToBytes(input);
String result = new String(decodedBytes, StandardCharsets.UTF_8);
return result;
}
public static byte[] base64UrlDecodeToBytes(String input)
{
Base64 decoder = new Base64(-1, null, true);
byte[] decodedBytes = decoder.decode(input);
return decodedBytes;
}
public static void main(String args[])
{
dumpJwtInfo();
validateToken();
}
public static void dump(String data)
{
System.out.println(data);
}
public static void dumpJwtInfo()
{
dump(ID_TOKEN_HEADER);
dump(ID_TOKEN_PAYLOAD);
}
public static void validateToken()
{
PublicKey publicKey = getPublicKey(MODULUS, EXPONENT);
byte[] data = (id_token_parts[0] + "." + id_token_parts[1]).getBytes(StandardCharsets.UTF_8);
try
{
boolean isSignatureValid = verifyUsingPublicKey(data, ID_TOKEN_SIGNATURE, publicKey);
System.out.println("isSignatureValid: " + isSignatureValid);
}
catch (GeneralSecurityException e)
{
e.printStackTrace();
}
}
public static PublicKey getPublicKey(String MODULUS, String EXPONENT)
{
byte[] nb = base64UrlDecodeToBytes(MODULUS);
byte[] eb = base64UrlDecodeToBytes(EXPONENT);
BigInteger n = new BigInteger(1, nb);
BigInteger e = new BigInteger(1, eb);
RSAPublicKeySpec rsaPublicKeySpec = new RSAPublicKeySpec(n, e);
try
{
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(rsaPublicKeySpec);
return publicKey;
}
catch (Exception ex)
{
throw new RuntimeException("Cant create public key", ex);
}
}
private static boolean verifyUsingPublicKey(byte[] data, byte[] signature, PublicKey pubKey) throws GeneralSecurityException
{
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initVerify(pubKey);
sig.update(data);
return sig.verify(signature);
}
}
Note if you're not opposed to using a third party library, I'd totally suggest using this, as it works great. I couldn't use it for business reasons, but was glad to find it as it helped me understand how this process works, validated an id_token, I'm sure in a much more robust way.
Also, to be certain this request was signed by the same client, ensure the aud parameter in the payload matches your own client key given to you by Salesforce.
As part of Spring Security OAuth, the Spring team has developed a library called Spring Security JWT that allows manipulation of JWTs, including decoding and verifying tokens.
See the following helper class for example:
https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-jwt/src/main/java/org/springframework/security/jwt/JwtHelper.java
The library is in version 1.0.0-RELEASE and available in the maven repo.

Categories