So this has worked for me as well! But can anyone help me with the C# decryption logic? Below, I'm sharing the Java decryption code and the equivalent C# code, too. The C# code throws the error "MAC check failed. Data may be tampered.", indicating that calcTag.SequenceEqual(tag)
is failing.
I'm also sharing the Java encryption logic here.
The Java Encryption Code:
package encrypt;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.Instant;
import java.util.Base64;
import javax.crypto.Cipher;
import org.bouncycastle.crypto.digests.Blake2bDigest;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.IESParameterSpec;
public class Encrypt {
public static void main(String[] args) throws Exception {
// plain-text request
String input = "thIS IS MANISH";
String subid = "SO29";
String aiId = "SA29";
String currentTime = String.valueOf(Instant.now().toEpochMilli());
String expiryTime = String.valueOf(Instant.now().toEpochMilli() +(Integer.parseInt("180000")));
String signature = signPayload(input, currentTime, expiryTime);
String protectedInfo = Base64.getEncoder().encodeToString(getProtectedDetails(subid,aiId, currentTime, expiryTime).getBytes());
String encryptedpayload = encryptECIES(input);
String finalPayload = "{\"payload\":\"" + encryptedpayload + "\",\"signatures\":[{\"signature\":\"" + signature +
"\",\"protectedInfo\":\"" + protectedInfo + "\"}]}";
System.out.println(finalPayload);
}
private static String getProtectedDetails(String subscriberId, String keyID, String currentTime, String expiryTime) {
return "keyId=\"" + subscriberId + "|" + keyID + "|" + "ecdsa" + "\",algorithm=\"ecdsa\",created=\"" +
currentTime +
"\",expires=\"" + expiryTime +
"\",headers=\" (created)(expires)digest\"";
}
public static String blake2b512(String input){
System.out.println(input);
// Create Blake2b digest
Blake2bDigest blakeHash = new Blake2bDigest(512);
blakeHash.update(input.getBytes(), 0, input.getBytes().length);
byte[] hashByte = new byte[blakeHash.getDigestSize()];
blakeHash.doFinal(hashByte, 0);
System.out.println("Blake2b-512 Hash: " );
String encodedString = Base64.getEncoder().encodeToString(hashByte);
System.out.println(encodedString);
return encodedString;
}
public static String signPayload(String input, String currentTime, String expiryTime) throws NoSuchAlgorithmException, SignatureException, NoSuchProviderException, InvalidAlgorithmParameterException, InvalidKeySpecException, InvalidKeyException {
String concatenated = "(created):" + currentTime + "\n"
+ "(expires):" + expiryTime + "\n" + "digest:BLAKE2b-512="
+ blake2b512(input);
System.out.println(concatenated);
// partner's private key
//String encodedPrivateKey = "TUlHSEFnRUFNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQkcwd2F3SUJBUVFnRlNsV0UwaS8wWC9iYVE0N1lMUUNST1BSTFFnMXRib1F4L2xQeW4xUUZwT2hSQU5DQUFRVHpqMUsvMmc5blZTU3hTR0o4VnpzWU90Ui9rVFN5WG02dXNGSUJNbHEzcHQ1Nzh6cjlCZkY0aTIzOGRodzhWQ05obi9jQmZwQTJoNGJFK3JoTkJ3NA==";
String encodedPrivateKey = "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgFSlWE0i/0X/baQ47YLQCROPRLQg1tboQx/lPyn1QFpOhRANCAAQTzj1K/2g9nVSSxSGJ8VzsYOtR/kTSyXm6usFIBMlq3pt578zr9BfF4i238dhw8VCNhn/cBfpA2h4bE+rhNBw4";
KeyFactory keyFactory = KeyFactory.getInstance("EC");
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(encodedPrivateKey));
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
// Original message to be signed
// Create a signature instance using SHA-512 with ECDSA
Signature ecdsaSignature = Signature.getInstance("SHA512withECDSA");
// Initialize the signature instance with the private key for signing
ecdsaSignature.initSign(privateKey);
// Supply the data to be signed
byte[] data = concatenated.getBytes(StandardCharsets.UTF_8);
ecdsaSignature.update(data);
// Generate the digital signature
byte[] digitalSignature = ecdsaSignature.sign();
// Convert the digital signature to Base64 for easy display and transmission
String base64Signature = Base64.getEncoder().encodeToString(digitalSignature);
System.out.println("Digital Signature (Base64): " + base64Signature);
return base64Signature;
}
public static String encryptECIES(String payLoad) throws Exception {
Security.addProvider(new BouncyCastleProvider());
//npci's public key
String key = "-----BEGIN PUBLIC KEY-----\r\n"
+ "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEa2MrinHuivU0kE3hyJxxgoq96/N\r\n"
+ "DiTw8KxI6A+WXSStWrKUPwLYHdzKw5Z314ry6D9lpkMZflTP0BeCIZRwuw==\r\n"
+ "-----END PUBLIC KEY-----\r\n"
+ "";
String base64PublicKey = key
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s", "");
KeyFactory keyFactory = KeyFactory.getInstance("EC");
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode((base64PublicKey)));
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
Cipher cipher = Cipher.getInstance("ECIESwithSHA512/NONE/NoPadding");
IESParameterSpec iesParamSpec = new IESParameterSpec(null, null, 256);
cipher.init(1, publicKey, iesParamSpec);
return Base64.getEncoder().encodeToString(cipher.doFinal(payLoad.getBytes()));
}
}
The Java Decryption Code:
package encrypt;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.Instant;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import org.bouncycastle.crypto.digests.Blake2bDigest;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.IESParameterSpec;
import org.json.JSONObject;
public class Decrypt {
public static void main(String[] args) throws Exception{
// encrypted payload
String input = "{\"payload\":\"BlZphrMQwR/8s6VKQtciQtZGRWH07Lgk7IJWk1QIywmq7WbV0myS8oH+URn1RJziEJ4nC5ShODAF2iOOTt8gPgsXT23Gkq4orFqiyL2I\",\"protectedInfo\":\"a2V5SWQ9IlNPMjl8U0EyOXxlY2RzYSIsYWxnb3JpdGhtPSJlY2RzYSIsY3JlYXRlZD0iMTc0OTQ2MTkxMzc0MSIsZXhwaXJlcz0iMTc0OTQ2MjIxMzc0MiIsaGVhZGVycz0iIChjcmVhdGVkKShleHBpcmVzKWRpZ2VzdCI=\"}]}";
JSONObject jsonObject = new JSONObject(input);
String payload = createDecryptionCipher(jsonObject.getString("payload"));
String protectedInfo = jsonObject.getJSONArray("signatures").getJSONObject(0).getString("protectedInfo");
String decodedProtectedInfo = new String(Base64.getDecoder().decode(protectedInfo.getBytes()));
String signature = jsonObject.getJSONArray("signatures").getJSONObject(0).getString("signature");
System.out.println("decodedProtectedInfo : " + decodedProtectedInfo);
System.out.println("payload : " + payload);
System.out.println("signature : " +signature);
//NPCI's public key
String encodedPublicKey = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEE849Sv9oPZ1UksUhifFc7GDrUf5E0sl5urrBSATJat6bee/M6/QXxeItt/HYcPFQjYZ/3AX6QNoeGxPq4TQcOA==";
PublicKey publicKey = convertToPublicKey(Base64.getDecoder().decode(encodedPublicKey));
boolean result = validateSignature(signature, decodedProtectedInfo, payload, publicKey);
if(result){
System.out.println("signature validation successfully");
System.out.println("payload: " + payload);
}else{
System.out.println("signature validation failed");
}
}
public static PublicKey convertToPublicKey(byte[] encodePubKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
KeyFactory keyFactory = KeyFactory.getInstance("EC");
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(encodePubKey);
return keyFactory.generatePublic(x509EncodedKeySpec);
}
private static String createDecryptionCipher(String input) throws NoSuchPaddingException, NoSuchAlgorithmException, InterruptedException,
InvalidKeySpecException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
Security.addProvider(new BouncyCastleProvider());
Cipher cipher = Cipher.getInstance("ECIESwithSHA512/NONE/NoPadding");
// partner's private key
String privateKey = "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg51083sOVh5xvX7cgK/pF3/v6hMhZFGYxMYoJwYNLtFuhRANCAAQRrYyuKce6K9TSQTeHInHGCir3r80OJPDwrEjoD5ZdJK1aspQ/Atgd3MrDlnfXivLoP2WmQxl+VM/QF4IhlHC7";
IESParameterSpec iesParamSpec = new IESParameterSpec(null, null,256);
cipher.init(2, getDecryptedPrivateKey(privateKey), iesParamSpec);
return new String(cipher.doFinal(Base64.getDecoder().decode(input.getBytes())));
}
private static PrivateKey getDecryptedPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
KeyFactory keyFactory = KeyFactory.getInstance("EC");
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey));
return keyFactory.generatePrivate(privateKeySpec);
}
public static boolean validateSignature(String signature, String protectedInfo, String payload, PublicKey key)
throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {
Map<String, String> timeStamp = getTimeStamps(protectedInfo);
validateExpiryTime(timeStamp.get("expiryTime"));
byte[] sign = Base64.getDecoder().decode(signature.getBytes());
byte[] hashValue = blakeHashing(payload);
String concatenated = "(created):" + timeStamp.get("createdTime") + "\n"
+ "(expires):" + timeStamp.get("expiryTime") + "\n" + "digest:BLAKE2b-512="
+ Base64.getEncoder().encodeToString(hashValue);
return verify(concatenated.getBytes(), sign, key);
}
private static boolean verify(byte[] hashValue, byte[] signature, PublicKey publicKey) throws NoSuchAlgorithmException,
InvalidKeyException, SignatureException {
Signature verifier = Signature.getInstance("SHA512withECDSA");
verifier.initVerify(publicKey);
verifier.update(hashValue);
return verifier.verify(signature);
}
public static byte[] blakeHashing(String input){
System.out.println(input);
// Create Blake2b digest
Blake2bDigest blakeHash = new Blake2bDigest(512);
blakeHash.update(input.getBytes(), 0, input.getBytes().length);
byte[] hashByte = new byte[blakeHash.getDigestSize()];
blakeHash.doFinal(hashByte, 0);
return hashByte;
}
private static void validateExpiryTime(String s) throws SignatureException {
long currentTime = Instant.now().toEpochMilli();
if (currentTime > Long.parseLong(s)) {
System.out.println("Signature Expired");
throw new SignatureException("Signature Expired");
}
}
private static Map<String, String> getTimeStamps(String decodedProtectedInfo) throws SignatureException {
String[] protectedVal = decodedProtectedInfo.split(",");
String[] currentTimeString = protectedVal[2].split("=");
String currentTimeStamp = currentTimeString[1].substring(1, currentTimeString[1].length() - 1);
String[] expireTimeString = protectedVal[3].split("=");
String expireTimeStamp = expireTimeString[1].substring(1, expireTimeString[1].length() - 1);
Map<String, String> map = new HashMap<>();
map.put("createdTime", currentTimeStamp);
map.put("expiryTime", expireTimeStamp);
return map;
}
}
The java equivalent C# code:
public static string DecryptECIES(string base64Input, ECPrivateKeyParameters privateKey)
{
byte[] inputBytes = Convert.FromBase64String(base64Input);
int ephLen = 65, tagLen = 64;
int ctLen = inputBytes.Length - ephLen - tagLen;
if (ctLen <= 0) throw new Exception("Invalid input.");
byte[] ephPubBytes = inputBytes.Take(ephLen).ToArray();
byte[] ciphertext = inputBytes.Skip(ephLen).Take(ctLen).ToArray();
byte[] tag = inputBytes.Skip(ephLen + ctLen).ToArray();
byte[] derivation = new byte[0];
byte[] encoding = new byte[0];
byte[] L = GetLengthTag(encoding);
ECDomainParameters ecParams = privateKey.Parameters;
Org.BouncyCastle.Math.EC.ECPoint q = ecParams.Curve.DecodePoint(ephPubBytes);
ECPublicKeyParameters ephPubKey = new ECPublicKeyParameters(q, ecParams);
ECDHBasicAgreement agreement = new ECDHBasicAgreement();
agreement.Init(privateKey);
BigInteger sharedSecret = agreement.CalculateAgreement(ephPubKey);
byte[] agreementBytes = Arrays.Concatenate(ephPubBytes, BigIntegers.AsUnsignedByteArray(agreement.GetFieldSize(), sharedSecret));
Kdf2BytesGenerator kdf = new Kdf2BytesGenerator(new Sha512Digest());
kdf.Init(new KdfParameters(agreementBytes, derivation));
byte[] KEnc = new byte[ciphertext.Length];
byte[] KMac = new byte[64];
byte[] K = new byte[KEnc.Length + KMac.Length];
kdf.GenerateBytes(K, 0, K.Length);
Array.Copy(K, 0, KMac, 0, KMac.Length);
Array.Copy(K, KMac.Length, KEnc, 0, KEnc.Length);
HMac mac = new HMac(new Sha512Digest());
mac.Init(new KeyParameter(KMac));
mac.BlockUpdate(ciphertext, 0, ciphertext.Length);
mac.BlockUpdate(encoding, 0, encoding.Length);
mac.BlockUpdate(L, 0, L.Length);
byte[] calcTag = new byte[mac.GetMacSize()];
mac.DoFinal(calcTag, 0);
if (!calcTag.SequenceEqual(tag))
throw new CryptographicException("MAC check failed. Data may be tampered.");
byte[] plaintext = new byte[ciphertext.Length];
for (int i = 0; i < ciphertext.Length; i++)
plaintext[i] = (byte)(ciphertext[i] ^ KEnc[i]);
return Encoding.UTF8.GetString(plaintext);
}