diff --git a/src/main/java/com/razorpay/Utils.java b/src/main/java/com/razorpay/Utils.java index 0beb2469..1353a781 100644 --- a/src/main/java/com/razorpay/Utils.java +++ b/src/main/java/com/razorpay/Utils.java @@ -29,16 +29,16 @@ public static boolean verifySubscription(JSONObject attributes, String apiSecret String payload = paymentId + '|' + subscriptionId; return verifySignature(payload, expectedSignature, apiSecret); } - + public static boolean verifyPaymentLink(JSONObject attributes, String apiSecret) - throws RazorpayException { - String expectedSignature = attributes.getString("razorpay_signature"); - String paymentLinkStatus = attributes.getString("payment_link_status"); - String paymentLinkId = attributes.getString("payment_link_id"); - String paymentLinkRefId = attributes.getString("payment_link_reference_id"); - String paymentId = attributes.getString("razorpay_payment_id"); - String payload = paymentLinkId + '|' + paymentLinkRefId + '|' + paymentLinkStatus + '|' + paymentId; - return verifySignature(payload, expectedSignature, apiSecret); + throws RazorpayException { + String expectedSignature = attributes.getString("razorpay_signature"); + String paymentLinkStatus = attributes.getString("payment_link_status"); + String paymentLinkId = attributes.getString("payment_link_id"); + String paymentLinkRefId = attributes.getString("payment_link_reference_id"); + String paymentId = attributes.getString("razorpay_payment_id"); + String payload = paymentLinkId + '|' + paymentLinkRefId + '|' + paymentLinkStatus + '|' + paymentId; + return verifySignature(payload, expectedSignature, apiSecret); } public static boolean verifyWebhookSignature(String payload, String expectedSignature, @@ -46,10 +46,23 @@ public static boolean verifyWebhookSignature(String payload, String expectedSign return verifySignature(payload, expectedSignature, webhookSecret); } + public static boolean verifyWebhookSignature(byte[] payload, String expectedSignature, + String webhookSecret) throws RazorpayException { + return verifySignature(payload, expectedSignature, webhookSecret); + } + public static boolean verifySignature(String payload, String expectedSignature, String secret) throws RazorpayException { String actualSignature = getHash(payload, secret); - return isEqual(actualSignature.getBytes(), expectedSignature.getBytes()); + return isEqual(actualSignature.getBytes(StandardCharsets.UTF_8), + expectedSignature.getBytes(StandardCharsets.UTF_8)); + } + + public static boolean verifySignature(byte[] payload, String expectedSignature, String secret) + throws RazorpayException { + String actualSignature = getHash(payload, secret); + return isEqual(actualSignature.getBytes(StandardCharsets.UTF_8), + expectedSignature.getBytes(StandardCharsets.UTF_8)); } public static String generateOnboardingSignature(JSONObject attributes, String secret) throws RazorpayException { @@ -68,8 +81,7 @@ public static String encrypt(String dataToEncrypt, String secret) throws Razorpa cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec); byte[] encryptedData = cipher.doFinal(dataToEncrypt.getBytes(StandardCharsets.UTF_8)); return bytesToHex(encryptedData); - } - catch (Exception e) { + } catch (Exception e) { throw new RazorpayException(e.getMessage()); } } @@ -87,12 +99,16 @@ public static String bytesToHex(byte[] bytes) { } public static String getHash(String payload, String secret) throws RazorpayException { + return getHash(payload.getBytes(StandardCharsets.UTF_8), secret); + } + + public static String getHash(byte[] payload, String secret) throws RazorpayException { Mac sha256_HMAC; try { sha256_HMAC = Mac.getInstance("HmacSHA256"); - SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256"); + SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); sha256_HMAC.init(secret_key); - byte[] hash = sha256_HMAC.doFinal(payload.getBytes()); + byte[] hash = sha256_HMAC.doFinal(payload); return new String(Hex.encodeHex(hash)); } catch (Exception e) { throw new RazorpayException(e.getMessage()); @@ -100,7 +116,8 @@ public static String getHash(String payload, String secret) throws RazorpayExcep } /** - * We are not using String.equals() method because of security issue mentioned in + * We are not using String.equals() method because of security issue mentioned + * in * StackOverflow * * @param a @@ -117,4 +134,4 @@ private static boolean isEqual(byte[] a, byte[] b) { } return result == 0; } -} +} \ No newline at end of file diff --git a/src/test/java/com/razorpay/UtilsTest.java b/src/test/java/com/razorpay/UtilsTest.java index f9697b30..abc25228 100644 --- a/src/test/java/com/razorpay/UtilsTest.java +++ b/src/test/java/com/razorpay/UtilsTest.java @@ -14,10 +14,11 @@ public class UtilsTest { /** * Verify razorpay payment signature + * * @throws RazorpayException */ @Test - public void verifyPaymentSignature() throws RazorpayException{ + public void verifyPaymentSignature() throws RazorpayException { JSONObject options = new JSONObject(); options.put("razorpay_order_id", "order_IEIaMR65cu6nz3"); options.put("razorpay_payment_id", "pay_IH4NVgf4Dreq1l"); @@ -28,10 +29,11 @@ public void verifyPaymentSignature() throws RazorpayException{ /** * Verify razorpay subscription + * * @throws RazorpayException */ @Test - public void verifySubscription() throws RazorpayException{ + public void verifySubscription() throws RazorpayException { JSONObject options = new JSONObject(); options.put("razorpay_subscription_id", "sub_ID6MOhgkcoHj9I"); options.put("razorpay_payment_id", "pay_IDZNwZZFtnjyym"); @@ -42,10 +44,11 @@ public void verifySubscription() throws RazorpayException{ /** * Verify razorpay payment link signature + * * @throws RazorpayException */ @Test - public void verifyPaymentLink() throws RazorpayException{ + public void verifyPaymentLink() throws RazorpayException { JSONObject options = new JSONObject(); options.put("payment_link_reference_id", "TSsd1989"); options.put("razorpay_payment_id", "pay_IH3d0ara9bSsjQ"); @@ -58,14 +61,15 @@ public void verifyPaymentLink() throws RazorpayException{ /** * Verify razorpay webhook signature + * * @throws RazorpayException */ @Test - public void verifyWebhookSignature() throws RazorpayException{ + public void verifyWebhookSignature() throws RazorpayException { String signature = "2fe04e22977002e6c7cb553adab8b460cb9e2a4970d5953cb27a8472752e3bbc"; String payload = "{\"a\":1,\"b\":2,\"c\":{\"d\":3}}"; String secret = "123456"; - assertTrue(Utils.verifyWebhookSignature(payload,signature, secret)); + assertTrue(Utils.verifyWebhookSignature(payload, signature, secret)); } @Test @@ -86,10 +90,42 @@ public void testGenerateOnboardingSignature() throws RazorpayException { } } + @Test + public void verifyWebhookSignatureUtf8Payload() throws Exception { + String payload = "{\"name\":\"café ₹ 😄\",\"notes\":\"हेलो\"}"; + String secret = "123456"; + + String signature = computeWebhookSignature(payload, secret); + + assertTrue(Utils.verifyWebhookSignature(payload, signature, secret)); + assertTrue(Utils.verifyWebhookSignature(payload.getBytes(StandardCharsets.UTF_8), signature, secret)); + } + + private String computeWebhookSignature(String payload, String secret) throws Exception { + javax.crypto.Mac sha256_HMAC = javax.crypto.Mac.getInstance("HmacSHA256"); + SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); + sha256_HMAC.init(secretKey); + byte[] hash = sha256_HMAC.doFinal(payload.getBytes(StandardCharsets.UTF_8)); + return bytesToHex(hash); + } + + private String bytesToHex(byte[] bytes) { + StringBuilder hexString = new StringBuilder(); + for (byte b : bytes) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + return hexString.toString(); + } + private String decryptData(String encryptedHexData, String secret) throws Exception { byte[] encryptedData = hexStringToByteArray(encryptedHexData); return decrypt(encryptedData, secret); } + public static String decrypt(byte[] encryptedData, String secret) throws Exception { byte[] keyBytes = secret.substring(0, 16).getBytes(StandardCharsets.UTF_8); SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); @@ -107,7 +143,7 @@ public static byte[] hexStringToByteArray(String s) { byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) - + Character.digit(s.charAt(i+1), 16)); + + Character.digit(s.charAt(i + 1), 16)); } return data; }