Get SAD Public Key
GET
/v1/rest/keys/sad
Description: Retrieves the public encryption key used for encrypting Sensitive Authentication Data (SAD) - specifically CVV/CVV2 security codes on credit cards.
What is this endpoint used for?
This endpoint provides the encryption key specifically for protecting CVV security codes:
- CVV Codes: Encrypt the 3-digit security code on the back of Visa, Mastercard, and Discover cards
- CVV2/CVC2: Encrypt the 4-digit security code on the front of American Express cards
- Card-Not-Present Transactions: Essential for online and phone orders where you need to verify the customer has the physical card
- PCI Compliance: Ensures CVV codes are never stored or transmitted in plain text
Critical Security Note:
- This key is ONLY for encrypting CVV/CVV2 security codes
- Do NOT use this key to encrypt credit card numbers (use the PAN key from
/v1/rest/keys/pan) - CVV codes must NEVER be stored - even encrypted - after transaction authorization
- Only use CVV codes for immediate transaction processing
Authentication
This endpoint requires bearer token authentication using the Authorization header.
Note: The bearer token must be a valid Cognito token obtained through the OAuth2 authentication flow.
Code Examples
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
public class PublicKey
{
[JsonProperty("keyId")]
public string KeyId { get; set; }
[JsonProperty("key")]
public string Key { get; set; }
[JsonProperty("exponent")]
public string Exponent { get; set; }
[JsonProperty("modulus")]
public string Modulus { get; set; }
}
public class SadKeyClient
{
private readonly HttpClient _httpClient;
private readonly string _baseUrl = "https://your-api-domain.com";
public SadKeyClient()
{
_httpClient = new HttpClient();
_httpClient.BaseAddress = new Uri(_baseUrl);
}
public async Task<PublicKey> GetSadKeyAsync()
{
try
{
// Make the GET request
HttpResponseMessage response = await _httpClient.GetAsync("/v1/rest/keys/sad");
// Ensure the request was successful
response.EnsureSuccessStatusCode();
// Read and deserialize the response
string responseBody = await response.Content.ReadAsStringAsync();
PublicKey sadKey = JsonConvert.DeserializeObject<PublicKey>(responseBody);
return sadKey;
}
catch (HttpRequestException e)
{
Console.WriteLine($"Request error: {e.Message}");
throw;
}
}
// Example: Encrypt a CVV code
public string EncryptCvv(string cvv, PublicKey sadKey)
{
// Validate CVV format
if (string.IsNullOrEmpty(cvv) || (cvv.Length != 3 && cvv.Length != 4))
{
throw new ArgumentException("CVV must be 3 or 4 digits");
}
// Use RSA encryption library to encrypt with the public key
// This is a placeholder - implement actual RSA encryption
// with PKCS#1 v1.5 padding using your preferred crypto library
// Example using System.Security.Cryptography
// return RsaEncrypt(cvv, sadKey.Key);
throw new NotImplementedException("Implement RSA encryption here");
}
}
// Example usage:
var client = new SadKeyClient();
PublicKey sadKey = await client.GetSadKeyAsync();
Console.WriteLine($"SAD Key ID: {sadKey.KeyId}");
// Encrypt a CVV code (remember: never store this!)
string encryptedCvv = client.EncryptCvv("123", sadKey);
// Use immediately in transaction, then discard
Imports System.Net.Http
Imports System.Threading.Tasks
Imports Newtonsoft.Json
Public Class PublicKey
<JsonProperty("keyId")>
Public Property KeyId As String
<JsonProperty("key")>
Public Property Key As String
<JsonProperty("exponent")>
Public Property Exponent As String
<JsonProperty("modulus")>
Public Property Modulus As String
End Class
Public Class SadKeyClient
Private ReadOnly _httpClient As HttpClient
Private ReadOnly _baseUrl As String = "https://your-api-domain.com"
Public Sub New()
_httpClient = New HttpClient()
_httpClient.BaseAddress = New Uri(_baseUrl)
End Sub
Public Async Function GetSadKeyAsync() As Task(Of PublicKey)
Try
' Make the GET request
Dim response As HttpResponseMessage = Await _httpClient.GetAsync("/v1/rest/keys/sad")
' Ensure the request was successful
response.EnsureSuccessStatusCode()
' Read and deserialize the response
Dim responseBody As String = Await response.Content.ReadAsStringAsync()
Dim sadKey As PublicKey = JsonConvert.DeserializeObject(Of PublicKey)(responseBody)
Return sadKey
Catch ex As HttpRequestException
Console.WriteLine($"Request error: {ex.Message}")
Throw
End Try
End Function
' Example: Encrypt a CVV code
Public Function EncryptCvv(cvv As String, sadKey As PublicKey) As String
' Validate CVV format
If String.IsNullOrEmpty(cvv) OrElse (cvv.Length <> 3 AndAlso cvv.Length <> 4) Then
Throw New ArgumentException("CVV must be 3 or 4 digits")
End If
' Use RSA encryption library to encrypt with the public key
' This is a placeholder - implement actual RSA encryption
' with PKCS#1 v1.5 padding
Throw New NotImplementedException("Implement RSA encryption here")
End Function
End Class
' Example usage:
Dim client As New SadKeyClient()
Dim sadKey As PublicKey = Await client.GetSadKeyAsync()
Console.WriteLine($"SAD Key ID: {sadKey.KeyId}")
' Encrypt CVV (never store this - use immediately!)
Dim encryptedCvv As String = client.EncryptCvv("123", sadKey)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
class PublicKey {
@SerializedName("keyId")
private String keyId;
@SerializedName("key")
private String key;
@SerializedName("exponent")
private String exponent;
@SerializedName("modulus")
private String modulus;
// Getters
public String getKeyId() { return keyId; }
public String getKey() { return key; }
public String getExponent() { return exponent; }
public String getModulus() { return modulus; }
}
public class SadKeyClient {
private final HttpClient httpClient;
private final String baseUrl;
private final Gson gson;
public SadKeyClient() {
this.baseUrl = "https://your-api-domain.com";
this.httpClient = HttpClient.newHttpClient();
this.gson = new Gson();
}
public PublicKey getSadKey() throws Exception {
// Construct the endpoint URL
String endpoint = baseUrl + "/v1/rest/keys/sad";
// Build the HTTP request
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(endpoint))
.header("Accept", "application/json")
.GET()
.build();
// Send the request and get response
HttpResponse<String> response = httpClient.send(
request,
HttpResponse.BodyHandlers.ofString()
);
// Check response status
if (response.statusCode() == 200) {
return gson.fromJson(response.body(), PublicKey.class);
} else {
throw new Exception("Request failed with status: " + response.statusCode());
}
}
// Example: Encrypt CVV code
public String encryptCvv(String cvv, PublicKey sadKey) {
// Validate CVV format
if (cvv == null || (cvv.length() != 3 && cvv.length() != 4)) {
throw new IllegalArgumentException("CVV must be 3 or 4 digits");
}
// Implement RSA encryption with PKCS#1 v1.5 padding
// using javax.crypto or BouncyCastle library
throw new UnsupportedOperationException("Implement RSA encryption here");
}
// Example usage
public static void main(String[] args) {
try {
SadKeyClient client = new SadKeyClient();
PublicKey sadKey = client.getSadKey();
System.out.println("SAD Key ID: " + sadKey.getKeyId());
// Encrypt CVV (NEVER store - use immediately!)
String encryptedCvv = client.encryptCvv("123", sadKey);
System.out.println("Encrypted CVV ready for immediate transmission");
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
e.printStackTrace();
}
}
}
require 'net/http'
require 'uri'
require 'json'
require 'openssl'
require 'base64'
class SadKeyClient
def initialize(base_url = 'https://your-api-domain.com')
@base_url = base_url
end
def get_sad_key
# Construct the endpoint URL
uri = URI("#{@base_url}/v1/rest/keys/sad")
# Create HTTP request
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if uri.scheme == 'https'
http.read_timeout = 30
request = Net::HTTP::Get.new(uri.path)
request['Accept'] = 'application/json'
# Send request and handle response
response = http.request(request)
case response.code.to_i
when 200
JSON.parse(response.body)
else
raise "Request failed with status: #{response.code} - #{response.message}"
end
rescue StandardError => e
puts "Request error: #{e.message}"
raise
end
# Example: Encrypt CVV code
def encrypt_cvv(cvv, sad_key)
# Validate CVV format
raise ArgumentError, 'CVV must be 3 or 4 digits' unless cvv&.match?(/^\d{3,4}$/)
# Import the public key
public_key_pem = sad_key['key']
public_key = OpenSSL::PKey::RSA.new(public_key_pem)
# Encrypt the CVV using RSA with PKCS#1 v1.5 padding
encrypted = public_key.public_encrypt(cvv, OpenSSL::PKey::RSA::PKCS1_PADDING)
# Return base64 encoded encrypted data
Base64.strict_encode64(encrypted)
end
end
# Example usage:
client = SadKeyClient.new
sad_key = client.get_sad_key
puts "SAD Key ID: #{sad_key['keyId']}"
# Encrypt CVV (NEVER store - use immediately in transaction!)
encrypted_cvv = client.encrypt_cvv('123', sad_key)
puts "Encrypted CVV ready for immediate use"
# IMPORTANT: Clear CVV from memory immediately after encryption
cvv = nil
encrypted_cvv = nil # After sending in transaction
Response Structure
| Field | Type | Description |
|---|---|---|
| keyId | string | Unique identifier for this key version |
| key | string | The complete public key in PEM format (base64 encoded) |
| exponent | string | The RSA public exponent (typically "AQAB" for value 65537) |
| modulus | string | The RSA modulus (the public component of the RSA key pair) |
Example Response
{
"keyId": "z9y8x7w6-v5u4-3210-zyxw-vu9876543210",
"key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzyxwvutsrqponmlkjihg\nfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA9876543210zyxwvutsrqponmlkjihgfed\ncbaZYXWVUTSRQPONMLKJIHGFEDCBA9876543210zyxwvutsrqponmlkjihgfedcbaZ\nYXWVUTSRQPONMLKJIHGFEDCBA9876543210zyxwvutsrqponmlkjihgfedcbaZYXWV\nUTSRQPONMLKJIHGFEDCBA\n-----END PUBLIC KEY-----",
"exponent": "AQAB",
"modulus": "zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA9876543210zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA9876543210zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA"
}
HTTP Status Codes
| Status Code | Description |
|---|---|
| 200 | Success - SAD public key retrieved successfully |
| 500 | Internal Server Error - Server encountered an error retrieving the key |
Understanding CVV/CVV2 Security Codes
What is a CVV Code?
| Card Type | Code Name | Digits | Location |
|---|---|---|---|
| Visa | CVV2 | 3 | Back of card, signature panel |
| Mastercard | CVC2 | 3 | Back of card, signature panel |
| Discover | CID | 3 | Back of card, signature panel |
| American Express | CID | 4 | Front of card, above card number |
Why CVV Codes Matter
- Cardholder Verification: Proves the customer has the physical card (not just the number)
- Fraud Prevention: Not stored in merchant databases, so data breaches don't expose CVV codes
- PCI Requirement: CVV codes must never be stored after authorization - not even encrypted
- Lower Fees: Providing CVV can reduce transaction fees by lowering fraud risk
When to Use the SAD Key
✅ DO Use SAD Key For:
- Card-Not-Present Transactions: Online purchases, phone orders, mail orders
- E-commerce Checkout: When customer enters CVV during payment
- One-Time Payments: Immediate transaction processing
- First Transaction: Initial verification when saving a payment method
❌ DO NOT Use SAD Key For:
- Recurring Payments: CVV is not used for saved payment method transactions
- ACH Transactions: Bank accounts don't have CVV codes
- Storage: Never encrypt and store CVV codes - use immediately then discard
- Card-Present Transactions: Physical terminal transactions don't require CVV
Complete Payment Workflow
Step-by-Step Guide
// Step 1: Fetch both PAN and SAD keys
var panKey = await GetPanKeyAsync();
var sadKey = await GetSadKeyAsync();
// Step 2: Collect payment information from customer
string cardNumber = "4111111111111111"; // Collect from secure form
string cvv = "123"; // Collect from secure form
string expiry = "1225"; // Collect from secure form
// Step 3: Validate input
if (!IsValidCardNumber(cardNumber)) { /* handle error */ }
if (!IsValidCvv(cvv)) { /* handle error */ }
// Step 4: Encrypt sensitive data
string encryptedCard = EncryptWithPanKey(cardNumber, panKey);
string encryptedCvv = EncryptWithSadKey(cvv, sadKey);
// Step 5: Clear plain text from memory
cardNumber = null;
cvv = null;
// Step 6: Send transaction request
var request = new TransactionRequest
{
EncryptedAccountNumber = encryptedCard,
EncryptedCvv2 = encryptedCvv,
CardExpiry = expiry,
Amount = new Amount { Dollars = 100.00m },
AccountHolderName = "John Doe"
};
var result = await ProcessTransactionAsync(merchantId, request);
// Step 7: Clear encrypted CVV from memory (don't store it!)
encryptedCvv = null;
PCI Compliance Rules for CVV
Critical PCI DSS Requirements:
- PCI DSS 3.2.3: Never store CVV codes after authorization - not even encrypted
- Immediate Use Only: CVV should be encrypted, sent in transaction, then immediately discarded
- No Logging: Never log CVV codes in application logs, debug output, or error messages
- Memory Clearing: Overwrite CVV data in memory immediately after use
- No Database Storage: Never insert CVV into any database table, even temporarily
Correct CVV Handling Timeline
- 0:00 - Customer enters CVV in payment form
- 0:01 - Application receives CVV (in memory only)
- 0:02 - Fetch SAD key from API
- 0:03 - Encrypt CVV with SAD key
- 0:04 - Clear plain text CVV from memory
- 0:05 - Send encrypted CVV in transaction request
- 0:06 - Receive transaction response
- 0:07 - Clear encrypted CVV from memory
- 0:08 - Display transaction result to customer
Total CVV lifetime: 8 seconds (in memory only, never persisted)
Common Mistakes to Avoid
| Mistake | Why It's Wrong | Correct Approach |
|---|---|---|
| Storing encrypted CVV in database | Violates PCI DSS 3.2.3 | Use CVV immediately, never store |
| Using PAN key to encrypt CVV | Wrong key, decryption will fail | Use SAD key for CVV, PAN key for card numbers |
| Logging CVV in error messages | Security breach, PCI violation | Never log sensitive data |
| Caching CVV for "retry" scenarios | PCI violation, security risk | Ask customer to re-enter CVV if retry needed |
| Sending CVV with saved payment method | Not required, creates security risk | Only send CVV for initial verification |
Testing
Test CVV Codes (Sandbox Environment)
- Any 3-digit value: Will work in sandbox for testing (e.g., "123", "999")
- Amex (4 digits): Use "1234" or "9999" for testing
- Production: Always use the actual CVV from the customer's card
Troubleshooting
"Invalid CVV" Error in Transaction
- Verify you encrypted with the SAD key (not the PAN key)
- Check CVV is 3 digits (or 4 for Amex)
- Ensure no spaces or special characters in CVV before encryption
- Confirm CVV was entered correctly by customer
"CVV Required" Error
- Some merchants require CVV for all card-not-present transactions
- Check merchant configuration settings
- Ensure
encryptedCvv2field is included in request
Encryption Fails
- Verify CVV is a string of digits (not integer)
- Check RSA padding is PKCS#1 v1.5 (not OAEP)
- Ensure public key is correctly imported from PEM format
- Try fetching a fresh SAD key
Related Endpoints
- GET /v1/rest/keys - Get both PAN and SAD keys in one request
- GET /v1/rest/keys/pan - Get the PAN key for encrypting card/account numbers
Additional Notes
- SAD stands for "Sensitive Authentication Data" - the payment industry term for CVV/CVV2/CVC2 security codes.
- The SAD key is separate from the PAN key to provide defense in depth - compromising one key doesn't expose both types of data.
- This endpoint returns the same SAD key as the
sadKeyfield in theGET /v1/rest/keysresponse. - CVV codes are sometimes called CVC, CVV2, CVC2, or CID depending on the card brand, but they all serve the same purpose.
- For recurring billing or subscription payments using saved payment methods, CVV is typically not required after the initial setup.
- Some card-present (physical terminal) transactions may capture CVV, but it's primarily designed for card-not-present scenarios.
- The requirement to never store CVV applies even to encrypted values - this is a strict PCI DSS rule to protect cardholders.