Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better and faster AES encryption for AA web rediction #1

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 165 additions & 43 deletions samples/java.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,68 @@
Sample Java code for XOR and encryption of the **ecreq** and **ecres** fields.

```text
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Base64;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Base64.getUrlDecoder;
import static java.util.Base64.getUrlEncoder;
import static javax.crypto.Cipher.DECRYPT_MODE;
import static javax.crypto.Cipher.ENCRYPT_MODE;

import java.security.SecureRandom;
import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class WebRedirection {
private static final int DEFAULT_PASSWORD_LENGTH = 32;
private static final int IV_LEN = 16;
private static final String ALLOWED_CHARS = "0123456789abcdefghijklmnopqrstuvwxyz-_ABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&*?@^_~";
private static final String AES_ALGO = "AES";
private static final String KEY_ALGO = "PBKDF2WithHmacSHA256";
private static final String AES_CBC_PKCS5 = "AES/CBC/PKCS5Padding";
private static final String secretKey = "ac12ghd75kf75r";
private static final String fi = "FIUID";
private static final SecureRandom RANDOM = new SecureRandom();

public static String encrypt(String strToEncrypt, String salt) {
final byte[] iv = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

/**
* Use this to generate a random password and share with AA/FIU which will be used as a symmetric key between FIU and AA.
* Password needs to be generated only once during the initial configuration of redirection between FIU and AA and has to be done at the same between AA and FIU.
* Password needs to be shared out of band. (i.e. secure e-mail or any other secure channel)
*
* @return
*/
public static String generateRamdomPassword() {
return RANDOM.ints(DEFAULT_PASSWORD_LENGTH, 0, ALLOWED_CHARS.length()).mapToObj(i -> ALLOWED_CHARS.charAt(i))
.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append).toString();
}

/**
* Generate random iv of 16 bytes and returns base 64 encoded String.
* Use this for every request/response encryption/decyption. Pass the iv value to the FIU/AA in the iv url parameter.
* @return
*/
public static String generateIV() {
byte[] iv = new byte[IV_LEN];
RANDOM.nextBytes(iv);
return Base64.getEncoder().encodeToString(iv);
}

/**
* Encrypt the ecreq/ecres using this method.
*
* @param strToEncrypt
* @param password
* @param salt
* @param ivString
* @return
*/
public static String encrypt(String strToEncrypt, String password, String salt, String ivString) {
final byte[] iv = Base64.getDecoder().decode(ivString);
IvParameterSpec ivspec = new IvParameterSpec(iv);

try {
SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_ALGO);
KeySpec spec = new PBEKeySpec(secretKey.toCharArray(), salt.getBytes(), 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKeySpec secretKey = new SecretKeySpec(tmp.getEncoded(), AES_ALGO);
HKDF hkdf = new HKDF();
byte[] key = hkdf.deriveSecrets(password.getBytes(), salt.getBytes(), null, 32);
SecretKeySpec secretKey = new SecretKeySpec(key, AES_ALGO);
Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5);
cipher.init(ENCRYPT_MODE, secretKey, ivspec);
return getUrlEncoder().encodeToString(cipher.doFinal(strToEncrypt.getBytes(UTF_8)));
Expand All @@ -45,15 +74,22 @@ public class WebRedirection {
return null;
}


public static String decrypt(String strToDecrypt, String salt) {
byte[] iv = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
/**
* Decrypt the ecreq/ecres using this method.
*
* @param strToDecrypt
* @param password
* @param salt
* @param ivString
* @return
*/
public static String decrypt(String strToDecrypt, String password, String salt, String ivString) {
final byte[] iv = Base64.getDecoder().decode(ivString);
IvParameterSpec ivspec = new IvParameterSpec(iv);
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_ALGO);
KeySpec spec = new PBEKeySpec(secretKey.toCharArray(), salt.getBytes(), 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKeySpec secretKey = new SecretKeySpec(tmp.getEncoded(), AES_ALGO);
HKDF hkdf = new HKDF();
byte[] key = hkdf.deriveSecrets(password.getBytes(), salt.getBytes(), null, 32);
SecretKeySpec secretKey = new SecretKeySpec(key, AES_ALGO);
Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5);
cipher.init(DECRYPT_MODE, secretKey, ivspec);
return new String(cipher.doFinal(getUrlDecoder().decode(strToDecrypt)));
Expand All @@ -64,55 +100,64 @@ public class WebRedirection {
}

public static void printUsage() {
System.out.println("Usage: java EncryptUtil <salt> <payload>");
System.out.println("Usage: java EncryptUtil <password> <salt> <payload>");
System.out.println(" where, 'reqdate' is to be used as salt & payload is as per the webview URL redirection spec doc.");
System.out.println(" e.g. java EncryptUtil 031120201803460 \"txnid=T1234&sessionid=S1234&srcref=Srcref1234&userid=acme@perfios-aa&redirect=https://example.com\"");
System.out.println(" e.g. java EncryptUtil nsrkGE&-p1Qh*Gn$3P0&Qt0s9!uz1U_I 031120201803460 \"txnid=T1234&sessionid=S1234&srcref=Srcref1234&userid=acme@perfios-aa&redirect=https://example.com\"");
System.out.println("");
}

public static void main(String[] args) {
String salt = null;
String payload = null;

if (args.length < 3) {
printUsage();
return;
}

String password = args[0];
String salt = args[1];
String payload = args[2];

if (args.length < 2) {
if (password == null || password.length() != DEFAULT_PASSWORD_LENGTH) {
System.out.println("Error: password length must be " + DEFAULT_PASSWORD_LENGTH);
printUsage();
return;
} else {
System.out.println("Using password: " + password);
}
salt = args[0];
payload = args[1];

if (salt == null || salt.length() == 0) {
System.out.println("Error: Salt must not be empty.");
printUsage();
return;
} else {
System.out.println("Using salt: " + salt);
}

if (payload == null || payload.length() == 0) {
System.out.println("Error: Payload must not be empty.");
printUsage();
return;
}

System.out.println("Using payload: " + payload);
String encData = encrypt(payload, salt);

// Generate IV for every redirection and pass in the iv url parameter
String ivString = generateIV();

String encData = encrypt(payload, password, salt, ivString);

if (encData != null) {
System.out.println("Encrypted & encoded data: " + encData);
System.out.println("Decrypted data (to verify): " + decrypt(encData, salt));
String decrypted = decrypt(encData, password, salt, ivString);
System.out.println("Decrypted data (to verify): " + decrypted);
System.out.println("Is decrypted data same as input?: " + (decrypted.equals(payload)));

String xoredFI = encryptValueToXor(fi, salt);
System.out.println("Xored FI: " + xoredFI);
System.out.println("Xored FI (reversed): " + decryptXoredValue(xoredFI, salt));
}
}

public static byte[] getRandomNonce(int numBytes) {
byte[] nonce = new byte[numBytes];
new SecureRandom().nextBytes(nonce);
return nonce;
}


/**
* Generate xored output of a using key.
*
Expand All @@ -136,5 +181,82 @@ public class WebRedirection {
return new String(Base64.getEncoder().encode(xor(value.getBytes(), key.getBytes())));
}
}
```

import java.io.ByteArrayOutputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;


/**
* This is a copy of the HKDF file from the libsignal-protocol-java
* which is a v2 implementation as per RFC 5869
* All credits go to the original author.
* For licensing, see the the original github location.
*
* https://github.com/signalapp/libsignal-protocol-java/blob/master/java/src/main/java/org/whispersystems/libsignal/kdf/HKDF.java
*/
public class HKDF {

private static final int HASH_OUTPUT_SIZE = 32;

public byte[] deriveSecrets(byte[] inputKeyMaterial, byte[] info, int outputLength) {
byte[] salt = new byte[HASH_OUTPUT_SIZE];
return deriveSecrets(inputKeyMaterial, salt, info, outputLength);
}

public byte[] deriveSecrets(byte[] inputKeyMaterial, byte[] salt, byte[] info, int outputLength) {
byte[] prk = extract(salt, inputKeyMaterial);
return expand(prk, info, outputLength);
}

private byte[] extract(byte[] salt, byte[] inputKeyMaterial) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(salt, "HmacSHA256"));
return mac.doFinal(inputKeyMaterial);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new AssertionError(e);
}
}

private byte[] expand(byte[] prk, byte[] info, int outputSize) {
try {
int iterations = (int) Math.ceil((double) outputSize / (double) HASH_OUTPUT_SIZE);
byte[] mixin = new byte[0];
ByteArrayOutputStream results = new ByteArrayOutputStream();
int remainingBytes = outputSize;

for (int i= getIterationStartOffset();i<iterations + getIterationStartOffset();i++) {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(prk, "HmacSHA256"));

mac.update(mixin);
if (info != null) {
mac.update(info);
}
mac.update((byte)i);

byte[] stepResult = mac.doFinal();
int stepSize = Math.min(remainingBytes, stepResult.length);

results.write(stepResult, 0, stepSize);

mixin = stepResult;
remainingBytes -= stepSize;
}

return results.toByteArray();
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new AssertionError(e);
}
}

protected int getIterationStartOffset(){
return 1;
}

}
```
8 changes: 4 additions & 4 deletions specification/encryption.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ description: Note about the encryption of data passed as URL parameters

All encryption must be done using AES 256. For the AES 256 encryption below will be used:

**IV** This can be 0
**SALT** This will be the reqdate or resdate
**FI** This will be the unique FIU ID \( i.e. the FIU entity id \)
**SECRETKEY** This will be the secret passphrase shared by the AA with the FIU.
**IV** - This should be generated every time during encryption and sent in the iv url parameter
**SALT** - This will be the reqdate or resdate
**FI** - This will be the unique FIU ID \( i.e. the FIU entity id \)
**SECRETKEY** - This will be the 32 character secret passphrase shared by the AA with the FIU.

## References

Expand Down
11 changes: 8 additions & 3 deletions specification/request-specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ Base64 encoded & encrypted request parameters \(see below\)
{% endapi-method-path-parameters %}
{% endapi-method-request %}

{% api-method-parameter name="iv" type="string" required=true %}
Base64 encoded random IV of 16 bytes. See java example for IV generation.
{% endapi-method-parameter %}
{% endapi-method-path-parameters %}
{% endapi-method-request %}

{% api-method-response %}
{% api-method-response-example httpCode=200 %}
{% api-method-response-example-description %}
Expand All @@ -51,9 +57,9 @@ Below are the parameters that will be encrypted using AES256 encryption algorith
| **Parameter name** | **Parameter type** | **Parameter description** |
| :--- | :--- | :--- |
| txnid | String | UUID txnid. Uniquely identifies a particular redirection event. This same value will be returned by AA to FIU in the ecres txnid field. |
| sessionid | String | Value that represents a state \(or session\) on the FIU end. This value is opaque to AA and will be returned as is to the FIU by AA in ecres sessionid field. |
| sessionid | String | Value that represents a 'state' \(or session\) on the FIU end. This value is opaque to AA and will be returned as is to the FIU by AA in ecres sessionid field. |
| userid | String | The AA user id \( Refer to A\] below \) |
| redirect | String | FIU Url that AA needs to call back after the user has provided consent in the AA domain. The value of this parameter should be URL encoded if the value contains url parameters. This is required in order to remove ambiguity between the parameters of ecreq \(separated by ‘&’ character\) with the parameters in the redirect url. |
| redirect | String | FIU Url that AA needs to call back after the user has provided consent in the AA domain. The value of this parameter should be URL encoded if the value contains url parameters. This is required in order to remove ambiguity between the parameters of ecreq \(separated by '&' character\) with the parameters in the redirect url. |
| srcref | String | Consent handle id, as returned by AA server to the /Consent request api invoked by FIU on the AA prior to this redirection call. |

## userid
Expand All @@ -65,4 +71,3 @@ The userid can be the mobile no with the aa handle e.g. 9999988888@aa
### For existing AA user

The userid should be with the AA handle e.g. userid@aa

12 changes: 9 additions & 3 deletions specification/response-specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ Unique AA identifier. This will be encrypted using Base64/XOR along with resdate
{% endapi-method-path-parameters %}
{% endapi-method-request %}

{% api-method-parameter name="iv" type="string" required=true %}
Base64 encoded random IV of 16 bytes. See java example for IV generation.
{% endapi-method-parameter %}
{% endapi-method-path-parameters %}
{% endapi-method-request %}

{% api-method-response %}
{% api-method-response-example httpCode=200 %}
{% api-method-response-example-description %}
Expand All @@ -53,11 +59,11 @@ Below are the parameters that will be encrypted using AES256 encryption algorith
| **Parameter name** | **Parameter type** | **Parameter description** |
| :--- | :--- | :--- |
| txnid | String | UUID txnid \( To be sent back from the request \) |
| sessionid | String | Value of sessionid received in the ecreq field in the request. |
| sessionid | String | Value of sessionid received in the 'ecreq' field in the request. |
| userid | String | The AA user id |
| status | String | The status ‘S’ for success and ‘F’ for failure |
| status | String | The status 'S' for success and 'F' for failure |
| errorcode | String | Refer the errorcodes table below |
| srcref | String | The consent handle id received in the ecreq ‘srcref’ field |
| srcref | String | The consent handle id received in the ecreq ‘srcref’ field |
dheerajkhardwal marked this conversation as resolved.
Show resolved Hide resolved

## Error codes

Expand Down