import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
public class Validator
{
public static void main(String[] args) {
// Sample signature header
String signatureHeader = "v-c-signature: t=1617830804768;keyId=bf44c857-b182-bb05-e053-34b8d30a7a72;
sig = CzHY47nzJgCSD / BREtSIb + 9 l / vfkaaL4qf9n8MNJ4CY = ";
String payload = "this is a decrypted payload";
// Convert the received signatureHeader into timestamp, keyId, and signature.
DigitalSignature companySignature = new DigitalSignature(signatureHeader);
// Check if the timestamp is within tolerance.
if (companySignature.isValidTimestamp()) {
// Client regenerates their signature using the timestamp from header and received payload.
byte[] signature = regenerateSignature(companySignature.getTimestamp(), payload);
// Check if the generated signature is same as signature received in header.
if (isValidSignature(signature, companySignature.getSignature())) {
System.out.println("Success - Signature is valid");
} else {
System.out.println("Error - Signatures do not match");
}
} else {
System.out.println("Error - timestamp is outside of tolerance level");
}
}
/**
* Compute HMAC with the SHA256 hash function.
* key is your private key.
* message is timestamp.payload.
* @return
*/
public static byte[] regenerateSignature(long timestamp, String message) {
String timestampedMessage = timestamp + "." + message;
String key = getSecurityKey();
// Generate the hash using key and message.
return calcHmacSHA256(Base64.getDecoder().decode(key), timestampedMessage.getBytes(StandardCharsets.UTF_8));
}
/**
* A mechanism to fetch the security key using keyId from a source. We're using Base64encoded version of
(test_key).
* @return
*/
private static String getSecurityKey() {
return "dGVzdF9rZXk="; //test_key
}
/**
* Generate SHA256 using secretKey and a message.
* Sample Hmacgenerator to test: https://8gwifi.org/hmacgen.jsp
* @param secretKey
* @param message
* @return
*/
private static byte[] calcHmacSHA256(byte[] secretKey, byte[] message) {
byte[] hmacSha256 = null;
try {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, "HmacSHA256");
mac.init(secretKeySpec);
hmacSha256 = mac.doFinal(message);
} catch (Exception e) {
throw new RuntimeException("Failed to calculate hmac-sha256", e);
}
return hmacSha256;
}
/**
* Compare the Base64 decoding of the signature with the signature received in the header.
* Sample encoder/decoder to test:
* https://www.base64encode.org/
* https://www.base64decode.org/
* @param bankSignature
* @param companySignature
* @return
*/
private static boolean isValidSignature(byte[] bankSignature, String companySignature) {
return Arrays.equals(bankSignature, Base64.getDecoder().decode(companySignature));
}
}
import java.time.Clock;
public class DigitalSignature {
private long timestamp;
private String keyId;
private String signature;
public DigitalSignature(String digitalSignature) {
try {
// Split the header by space. The first part is the key "v-c-signature". The second part is the actual
signature.
String signature = digitalSignature.split(" ")[1];
// Separate the actual signature by semicolon. This creates 3 parts (timestamp, keyId, sig).
String[] signatureParts = signature.split(";");
// The timestamp section is the first block. Split the timestamp section by = sign and actual timestamp is
in the second block.
this.timestamp = Long.parseLong(signatureParts[0].split("=")[1]);
// The keyId section is the second block. Split the keyId section by = sign and actual keyId is in the
second block
this.keyId = signatureParts[1].split("=")[1];
// The digital signature is the third block. Split digital signature by = sign and actual signature is
in the second block. This is Base64 encoded.
this.signature = signatureParts[2].split("=")[1];
} catch (Exception e) {
System.out.println("Invalid digital signature format");
}
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public String getKeyId() {
return keyId;
}
public void setKeyId(String keyId) {
this.keyId = keyId;
}
public String getSignature() {
return signature;
}
public void setSignature(String signature) {
this.signature = signature;
}
/**
* Using a tolerance of 60 mins
* Compute the current time in UTC and make sure the timestamp was generated within the tolerance period.
* UTC millis generator to test: https://currentmillis.com/
*
* @return
*/
public boolean isValidTimestamp() {
long tolerance = 60 * 60 * 1000 L; //60 mins
// return Clock.systemUTC().millis() - timestamp < tolerance; // Enable this if you want timestamp
validation.
return true;
}
}