Golang
This guide shows you how to decrypt sensitive card details using Go. The decryption process uses Rivest-Shamir-Adleman Optimal Asymmetric Encryption Padding (RSA-OAEP) to unwrap the Advanced Encryption Standard (AES) key. Then Advanced Encryption Standard 256-bit Galois/Counter Mode (AES-256-GCM) is used to decrypt the card data.
Prerequisites
Before you begin, you need:
- Go 1.16 or later installed on your system
- Your RSA private key, which was generated when you created the key pair
- An encrypted response from the View card sensitive details endpoint or the Get card PIN endpoint
Understanding the encrypted response
The View card sensitive details and the Get card PIN endpoints return three base64-encoded values:
| Field | Description |
|---|---|
| encrypted_key | AES-256 key encrypted with your RSA public key |
| nonce | 12-byte initialization vector for AES-GCM decryption |
| ciphertext | Encrypted card details |
Parse the RSA private key
Load your PEM-formatted RSA private key. The function supports both PKCS#1 and PKCS#8 formats:
import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
)
func parseRSAPrivateKeyPEM(privPEM []byte) (*rsa.PrivateKey, error) {
block, _ := pem.Decode(privPEM)
if block == nil {
return nil, errors.New("invalid private key PEM")
}
// Try PKCS#1
if priv, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {
return priv, nil
}
// Try PKCS#8
if key, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil {
if rsaKey, ok := key.(*rsa.PrivateKey); ok {
return rsaKey, nil
}
return nil, errors.New("private key is not RSA")
}
return nil, errors.New("failed to parse RSA private key PEM")
}
Decode the base64 values
Convert all three base64-encoded values from the API response:
import "encoding/base64"
encryptedKey, err := base64.StdEncoding.DecodeString(payload["encrypted_key"])
nonce, err := base64.StdEncoding.DecodeString(payload["nonce"])
ciphertext, err := base64.StdEncoding.DecodeString(payload["ciphertext"])
Decrypt the AES key
Use your RSA private key to decrypt the AES key. The decryption uses RSA-OAEP with SHA-256:
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
)
aesKey, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, priv, encryptedKey, nil)
Decrypt the card details
Create an AES-GCM cipher and decrypt the card details:
import (
"crypto/aes"
"crypto/cipher"
)
block, err := aes.NewCipher(aesKey)
gcm, err := cipher.NewGCM(block)
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
Complete decryption code
The following is the complete implementation that combines all the steps. It includes a helper function to parse the RSA private key and the main decryption function:
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
)
func parseRSAPrivateKeyPEM(privPEM []byte) (*rsa.PrivateKey, error) {
block, _ := pem.Decode(privPEM)
if block == nil {
return nil, errors.New("invalid private key PEM")
}
// Try PKCS#1
if priv, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {
return priv, nil
}
// Try PKCS#8
if key, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil {
if rsaKey, ok := key.(*rsa.PrivateKey); ok {
return rsaKey, nil
}
return nil, errors.New("private key is not RSA")
}
return nil, errors.New("failed to parse RSA private key PEM")
}
func DecryptCardDetails(privPEM []byte, payload map[string]string) (string, error) {
// Step 1: Parse RSA private key
priv, err := parseRSAPrivateKeyPEM(privPEM)
if err != nil {
return "", fmt.Errorf("parse private key: %w", err)
}
// Step 2: Decode base64 values
encryptedKey, err := base64.StdEncoding.DecodeString(payload["encrypted_key"])
if err != nil {
return "", fmt.Errorf("decode encrypted_key: %w", err)
}
nonce, err := base64.StdEncoding.DecodeString(payload["nonce"])
if err != nil {
return "", fmt.Errorf("decode nonce: %w", err)
}
ciphertext, err := base64.StdEncoding.DecodeString(payload["ciphertext"])
if err != nil {
return "", fmt.Errorf("decode ciphertext: %w", err)
}
// Step 3: Decrypt the AES key using RSA-OAEP
aesKey, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, priv, encryptedKey, nil)
if err != nil {
return "", fmt.Errorf("rsa decrypt aes key: %w", err)
}
// Step 4: Decrypt using AES-256-GCM
block, err := aes.NewCipher(aesKey)
if err != nil {
return "", fmt.Errorf("aes cipher: %w", err)
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", fmt.Errorf("aes-gcm: %w", err)
}
if len(nonce) != gcm.NonceSize() {
return "", errors.New("invalid nonce size")
}
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return "", fmt.Errorf("gcm decrypt: %w", err)
}
return string(plaintext), nil
}
Usage example
The following example shows how to use the DecryptCardDetails function with your private key and an encrypted response:
func main() {
privateKey := `
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA5llQr/KNtRhTn+2LJMwd5H62QUdMZ8Gq25LDpZRaosYbsoxH
... your private key content ...
-----END RSA PRIVATE KEY-----
`
// Response from the View card sensitive details endpoint
encryptedPayload := map[string]string{
"ciphertext": "kJR3z/bVTMTjiXMIQ8ha/eN/69q5vMr1sm6o1iKnCOixCB98tvp4WNsh9YCFXqNEPWfTkuiQ",
"encrypted_key": "zmhGe7OkCl2e5lNaSU6rw28u1UvzqWyncHc+4fKfn+GRBua+zleDdaMfHctTPqK0...",
"nonce": "W5lMRrL2yKSyWgfq",
}
cardDetails, err := DecryptCardDetails([]byte(privateKey), encryptedPayload)
if err != nil {
fmt.Printf("Decryption failed: %v\n", err)
return
}
fmt.Printf("Card details: %s\n", cardDetails)
}