Decrypt with jasypt after retrieving the encrypted value in Http Request - java

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++;
}
}
}

Related

PropertyPlaceholderConfigurer and PropertySourcesPlaceholderConfigurer run differently

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.

Fernet class encryption in python and decryption in java not working

I am trying to write a code for encryption in Python and decryption in Java but I am getting an error.
I am using cryptography.fernet in python to encrypt a file and when I use Fernet Java for decryption it shows an error.
Here is my python code:
from cryptography.fernet import Fernet
key = Fernet.generate_key()
cipher_suite = Fernet(key)
with open("key.txt", "wb") as f:
f.write(key)
with open("read_plain_text_from_here.txt", "r") as f:
encoded_text = f.read().encode()
cipher_text = cipher_suite.encrypt(encoded_text)
with open("write_cipher_text_here.txt", "wb") as f:
f.write(cipher_text)
with open("write_cipher_text_here.txt", "rb") as f:
cipher_text = f.read()
with open("key.txt", "rb") as f:
decryption_key = f.read()
with open("write_plain_text_here.txt", "wb") as f:
cipher_suite = Fernet(decryption_key)
f.write(cipher_suite.decrypt(cipher_text))
Here is my java code:
package encryptapp;
import com.macasaet.fernet.*;
public class Decrypt
{
public static void main(String args[])
{
final Key key = new Key("***key i got from python**");
final Token token = Token.fromString("***cipher text i got from python***");
final Validator<String> validator = new StringValidator() {};
final String payload = token.validateAndDecrypt(key, validator);
System.out.println("Payload is " + payload);
}
}
The error in Java that I get is:
Exception in thread "main" com.macasaet.fernet.TokenExpiredException: Token is expired
at com.macasaet.fernet.Token.validateAndDecrypt(Token.java:240)
at com.macasaet.fernet.Validator.validateAndDecrypt(Validator.java:104)
at com.macasaet.fernet.Token.validateAndDecrypt(Token.java:218)
at encryptapp.Decrypt.main(Decrypt.java:60)
LINKS for docs:
Python: https://cryptography.io/en/latest/
Java: https://github.com/l0s/fernet-java8/blob/master/README.md
The fernet-java8 class does not have an explicit TTL argument for decryption like the python class does. Instead, it has a default of 60 seconds. You need to override the getTimeToLive() method of the Validator interface to specify a custom TTL. If you want to set the TTL to "forever", which is equivalent to the keyword argument ttl=None in python fernet, do something like this:
import java.time.Duration;
import java.time.Instant;
.
.
.
#Override
final Validator < String > validator = new StringValidator() {
public TemporalAmount getTimeToLive() {
return Duration.ofSeconds(Instant.MAX.getEpochSecond());
}
};

Token validation failed Visa X-pay

I need to tweak an API https://sandbox.api.visa.com/cybersource/payments/flex/v1/keys?apikey={apikey}
I am imitating the official document X-Pay Token,but it fail with "Token validation failed" error.
{
"responseStatus": {
"status": 401,
"code": "9159",
"severity": "ERROR",
"message": "Token validation failed",
"info": ""
}
}
Below is my x-pay-token generation code.
import java.math.BigInteger;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SignatureException;
public class T {
private static String resoucePath = "payments/flex/v1/keys";
private static String queryString = "apikey=6DC0NMXO53QQFE6NFOLE213HXA-pvG6xE-1NtuCd5oOQr-O-s";
private static String requestBody = "{encryptionType:RsaOaep256}";
private static String sharedSecret = "gAynzAGf89+V}3{Q4Jx5cp-/R#Y#PEv#1XvxnjQC";
public static void main(String[] args) throws SignatureException {
System.out.println(T.generateXpaytoken(resoucePath, queryString, requestBody, sharedSecret));
}
public static String generateXpaytoken(String resourcePath, String queryString, String requestBody, String sharedSecret) throws SignatureException {
String timestamp = timeStamp();
String beforeHash = timestamp + resourcePath + queryString + requestBody;
String hash = hmacSha256Digest(beforeHash, sharedSecret);
String token = "xv2:" + timestamp + ":" + hash;
return token;
}
private static String timeStamp() {
return String.valueOf(System.currentTimeMillis() / 1000L);
}
private static String hmacSha256Digest(String data, String sharedSecret) throws SignatureException {
return getDigest("HmacSHA256", sharedSecret, data, true);
}
private static String getDigest(String algorithm, String sharedSecret, String data, boolean toLower) throws SignatureException {
try {
Mac sha256HMAC = Mac.getInstance(algorithm);
SecretKeySpec secretKey = new SecretKeySpec(sharedSecret.getBytes(StandardCharsets.UTF_8), algorithm);
sha256HMAC.init(secretKey);
byte[] hashByte = sha256HMAC.doFinal(data.getBytes(StandardCharsets.UTF_8));
String hashString = toHex(hashByte);
return toLower ? hashString.toLowerCase() : hashString;
} catch (Exception e) {
throw new SignatureException(e);
}
}
private static String toHex(byte[] bytes) {
BigInteger bi = new BigInteger(1, bytes);
return String.format("%0" + (bytes.length << 1) + "X", bi);
}
}
somebody can help me please?
The URL you are using:
https://sandbox.api.visa.com/cybersource/payments/flex/v1/keys?apikey={apikey}
Should be:
https://sandbox.api.visa.com/cybersource/payments/flex/v1/keys?apikey=6DC0NMXO53QQFE6NFOLE213HXA-pvG6xE-1NtuCd5oOQr-O-s
As on Feb 2020, If some one still has issue below points can help resolve issue.
Please make sure you have generated API Key and Secret for your sandbox project.
You can get these details in Dashboard -> Project -> Credentials -> Inbound and Authentication Keys -> API Key / Secret.
Please check the "Status" of the Key which should be Active.
If your "Credentials" tab does not have details for "Inbound and Authentication Keys" Please make sure to add the respective API then this section automatically appears.
Visa has "Visa Developer Center PlayGround" [similar to SoapUI/Postman] tool where you can easily test your API's. Unfortunately this is only supported with Windows as on Feb 2020, In future they may release the same for Mac/Linux too.
You can find this tool in Dashboard -> Project -> Assets -> Bottom of the page.

Java file encoding magic

Strange thing happened in Java Kingdom...
Long story short: I use Java API V3 to connect to QuickBooks and fetch the data form there (services for example).
Everything goes fine except the case when a service contains russian symbols (or probably non-latin symbols).
Here is Java code that does it (I know it's far from perfect)
package com.mde.test;
import static com.intuit.ipp.query.GenerateQuery.$;
import static com.intuit.ipp.query.GenerateQuery.select;
import java.util.LinkedList;
import java.util.List;
import com.intuit.ipp.core.Context;
import com.intuit.ipp.core.ServiceType;
import com.intuit.ipp.data.Item;
import com.intuit.ipp.exception.FMSException;
import com.intuit.ipp.query.GenerateQuery;
import com.intuit.ipp.security.OAuthAuthorizer;
import com.intuit.ipp.services.DataService;
import com.intuit.ipp.util.Config;
public class TestEncoding {
public static final String QBO_BASE_URL_SANDBOX = "https://sandbox-quickbooks.api.intuit.com/v3/company";
private static String consumerKey = "consumerkeycode";
private static String consumerSecret = "consumersecretcode";
private static String accessToken = "accesstokencode";
private static String accessTokenSecret = "accesstokensecretcode";
private static String appToken = "apptokencode";
private static String companyId = "companyidcode";
private static OAuthAuthorizer oauth = new OAuthAuthorizer(consumerKey, consumerSecret, accessToken, accessTokenSecret);
private static final int PAGING_STEP = 500;
public static void main(String[] args) throws FMSException {
List<Item> res = findAllServices(getDataService());
System.out.println(res.get(1).getName());
}
public static List<Item> findAllServices(DataService service) throws FMSException {
Item item = GenerateQuery.createQueryEntity(Item.class);
List<Item> res = new LinkedList<>();
for (int skip = 0; ; skip += PAGING_STEP) {
String query = select($(item)).skip(skip).take(PAGING_STEP).generate();
List<Item> items = (List<Item>)service.executeQuery(query).getEntities();
if (items.size() > 0)
res.addAll(items);
else
break;
}
System.out.println("All services fetched");
return res;
}
public static DataService getDataService() throws FMSException {
Context context = getContext();
if (context == null) {
System.out.println("Context is null, something wrong, dataService also will null.");
return null;
}
return getDataService(context);
}
private static Context getContext() {
try {
return new Context(oauth, appToken, ServiceType.QBO, companyId);
} catch (FMSException e) {
System.out.println("Context is not loaded");
return null;
}
}
protected static DataService getDataService(Context context) throws FMSException {
DataService service = new DataService(context);
Config.setProperty(Config.BASE_URL_QBO, QBO_BASE_URL_SANDBOX);
return new DataService(context);
}
}
This file is saved in UTF-8. And it prints something like
All services fetched
Сэрвыс, отнюдь
But! When I save this file in UTF-8 with BOM.... I get the correct data!
All services fetched
Сэрвыс, отнюдь
Does anybody can explain what is happening? :)
// I use Eclipse to run the code
You are fetching data from a system that doesn't share the same byte ordering as you, so when you save the file with BOM, it adds enough information in the file that future programs will read it in the remote system's byte ordering.
When you save it without BOM, it wrote the file in the remote system's byte ordering without any indication of the stored byte order, so when you read it you read it with the local system's (different) byte order. This jumbles up the bytes within the multi-byte characters, making the output appear as nonsense.

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