Skip to content

fix: use random nonce per call in AES-GCM onboarding signature (ISS-2528895)#355

Open
rzp-slash[bot] wants to merge 1 commit into
masterfrom
fix/aes-gcm-nonce-reuse
Open

fix: use random nonce per call in AES-GCM onboarding signature (ISS-2528895)#355
rzp-slash[bot] wants to merge 1 commit into
masterfrom
fix/aes-gcm-nonce-reuse

Conversation

@rzp-slash
Copy link
Copy Markdown

@rzp-slash rzp-slash Bot commented Jun 2, 2026

Summary

  • Vulnerability: AES-GCM nonce reuse in generateOnboardingSignature (HackerOne #3754503 / ISS-2528895)
  • Root cause: encrypt() used the first 12 bytes of the AES key as a fixed IV — identical for every encryption call with the same partner secret
  • Impact: An attacker who captures just 2 onboarding_signature values from partner URLs (browser history, logs, Sentry) can XOR the ciphertexts to recover the keystream and forge valid signatures for arbitrary submerchant IDs — without ever knowing the partner's secret key

Fix

Replace the static IV with SecureRandom.nextBytes(iv) and prepend the nonce to the output so the Razorpay backend can extract it for decryption.

Before:

byte[] iv = new byte[12];
System.arraycopy(keyBytes, 0, iv, 0, 12); // static — same every call
// ...
return bytesToHex(encryptedData); // hex(ciphertext || tag)

After:

byte[] iv = new byte[12];
new SecureRandom().nextBytes(iv); // fresh random nonce per call
// ...
byte[] combined = new byte[iv.length + encryptedData.length];
System.arraycopy(iv, 0, combined, 0, iv.length);
System.arraycopy(encryptedData, 0, combined, iv.length, encryptedData.length);
return bytesToHex(combined); // hex(iv[12] || ciphertext || tag[16])

New output format: hex(iv[12 bytes] || ciphertext || tag[16 bytes])
The receiver reads the first 24 hex chars (12 raw bytes) as the IV before decrypting.

Companion PRs (same fix, same format across all partner SDKs)

Test plan

  • Verify generateOnboardingSignature produces a different hex string on every call with the same input
  • Verify output length = 24 (iv) + 2×(plaintext_len) + 32 (tag) hex chars
  • Verify Razorpay backend successfully decrypts the new format
  • Confirm two signatures XORed no longer reveal plaintext (keystream is not reused)

🤖 Generated with Claude Code

Replaces the static IV (first 12 bytes of the key) with a fresh SecureRandom-generated 12-byte nonce for every encryption call.

The old approach caused AES-GCM nonce reuse: an attacker who collects two onboarding_signature values from partner URLs can XOR the ciphertexts to cancel the keystream and forge a valid signature for any submerchant ID without knowing the partner secret key (NIST SP 800-38D §8.3 Forbidden Attack).

New output format: hex(iv[12] || ciphertext || tag[16])
The receiver reads the first 24 hex chars as the IV before decrypting.

Reported via HackerOne #3754503 (ISS-2528895), SLA: 2026-06-05.

Co-authored-by: ankitdas13 <ankit.das@razorpay.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant