Goodays End-to-End Encryption Guide
If you wish to secure user data flowing to Goodays, you can encrypt sensitive parameters (like cz_user, cz_x_, etc.) using AES-256-CBC.
This guide provides the technical specifications and code examples to implement this encryption correctly.
Technical Specifications
| Parameter | Value | Example |
|---|---|---|
| Algorithm | AES | - |
| Mode | CBC (Cipher Block Chaining) | - |
| Key Size | 256 bits (32 bytes) | MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY= (Base64) |
| IV Size | 128 bits (16 bytes) | MDEyMzQ1Njc4OWFiY2RlZg== (Base64) |
| Padding | Custom (0x1C) - Do not use PKCS#7 | ...data\x1c\x1c\x1c |
| Output Encoding | Base64 string, then URL Encoded | Sww0ZtK5... |
Non-Standard Padding
Please read this carefully before implementation.
Our system uses a custom padding character to complete AES blocks:
- Character:
\34(Octal) - Hex Value:
0x1C - Decimal Value:
28
Most standard AES libraries (OpenSSL, Crypto, etc.) default to PKCS#7 padding.
You MUST disable default padding (PKCS#7) and implement the manual padding logic described below. Failure to do so will result in decryption errors on the Goodays side.
Encryption Workflow
- Prepare Data: Convert your string to UTF-8 bytes.
- Apply Padding: Calculate the number of missing bytes to reach a multiple of 16. Append the character 0x1C repeatedly.
- Encrypt: Encrypt the padded bytes using your Key and IV.
- Encode:
- Convert the encrypted binary result to a Base64 string.
- URL Encode the Base64 string.
Reference Implementation
The following keys are for testing purposes only. Production keys will be communicated separately.
- Test Key (Base64):
MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=- (Decodes to:
0123456789abcdef0123456789abcdef)
- (Decodes to:
- Test IV (Base64):
MDEyMzQ1Njc4OWFiY2RlZg==- (Decodes to:
0123456789abcdef)
- (Decodes to:
Expected Output for Validation
Input String:
Tatsuo|Kusakabe|[email protected]|0612345789|123456789
Encrypted Result (URL Encoded):
Sww0ZtK5thbCpUeYSDe63w0Jiiv%2FgkadCJZN0LXJvSe1234j3Yk8NqZSFSiDreotkn5qw66Yr3RImtcg%2B4eBzCuNhPPwtcsbs9huAupW0vqLxu%2B1b3q8XMP2hx
Code Examples
These examples are provided as a rough indication of how encryption should be set up on your side. We do not guarantee compatibility with your IT system.
In each code, please replace
<KEY>and<IV>with your dedicated values.
<?php
define('CIPHER_METHOD', 'AES-256-CBC');
// Configuration
define('KEY_B64', '<KEY>');
define('IV_B64', '<IV>');
define('PADDING_CHAR', "\x1C");
function validateBase64($str, $name) {
// strict mode = true returns false on failure
$decoded = base64_decode($str, true);
if ($decoded === false) {
echo "❌ CONFIG ERROR: The $name provided is not valid Base64.\n";
exit(1);
}
return $decoded;
}
function encryptGoodays($text) {
// Decode Keys with Validation
$key = validateBase64(KEY_B64, "KEY");
$iv = validateBase64(IV_B64, "IV");
// 1. Manual Padding
$blockSize = 16;
$pad = $blockSize - (strlen($text) % $blockSize);
$padded_text = $text . str_repeat(PADDING_CHAR, $pad);
// 2. Encrypt
// Note: Use OPENSSL_ZERO_PADDING to prevent automatic PKCS#7 padding.
$encrypted = openssl_encrypt(
$padded_text,
CIPHER_METHOD,
$key,
OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING,
$iv
);
// 3. Base64 -> URL Encode
return urlencode(base64_encode($encrypted));
}
function decryptGoodays($text) {
$key = validateBase64(KEY_B64, "KEY");
$iv = validateBase64(IV_B64, "IV");
// 1. URL Decode -> Base64 Decode
$encrypted_data = base64_decode(urldecode($text));
// 2. Decrypt
$decrypted_padded = openssl_decrypt(
$encrypted_data,
CIPHER_METHOD,
$key,
OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING,
$iv
);
// 3. Remove Padding
return rtrim($decrypted_padded, PADDING_CHAR);
}
// Test
echo "\n--- TEST START ---\n";
$msg = "Tatsuo|Kusakabe|[email protected]|0612345789|123456789";
echo "Original: " . $msg . "\n";
// Only run if keys are configured
if (KEY_B64 === '<KEY>') {
echo "Status: ⚠️ SKIPPED - Please configure KEY_B64 and IV_B64 before running.\n";
} else {
$encrypted = encryptGoodays($msg);
echo "Encrypted: " . $encrypted . "\n";
$decrypted = decryptGoodays($encrypted);
echo "Decrypted: " . $decrypted . "\n";
if ($msg === $decrypted) {
echo "Status: ✅ SUCCESS - Decryption matches original\n";
} else {
echo "Status: ❌ FAILED - Decryption mismatch\n";
}
}
echo "--- TEST END ---\n";
?>import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.net.URLEncoder;
import java.net.URLDecoder;
public class GoodaysEncryption {
// Configuration (Base64 Strings)
private static final String KEY_B64 = "<KEY>";
private static final String IV_B64 = "<IV>";
// Custom Padding Character 0x1C
private static final byte PADDING_VAL = 0x1C;
private static byte[] decodeBase64(String val, String name) {
try {
return Base64.getDecoder().decode(val);
} catch (IllegalArgumentException e) {
System.err.println("❌ CONFIG ERROR: The " + name + " provided is not valid Base64.");
System.exit(1);
return null;
}
}
public static String encrypt(String value) throws Exception {
byte[] keyBytes = decodeBase64(KEY_B64, "KEY");
byte[] ivBytes = decodeBase64(IV_B64, "IV");
// 1. Prepare Data and Calculate Padding
byte[] inputBytes = value.getBytes(StandardCharsets.UTF_8);
int blockSize = 16;
int paddingLength = blockSize - (inputBytes.length % blockSize);
int totalLength = inputBytes.length + paddingLength;
// 2. Create Padded Array (Manual Padding)
byte[] paddedBytes = Arrays.copyOf(inputBytes, totalLength);
for (int i = inputBytes.length; i < totalLength; i++) {
paddedBytes[i] = PADDING_VAL;
}
// 3. Encrypt (Use NoPadding to prevent automatic PKCS7)
// Note: Java defaults to PKCS5Padding. We use NoPadding and handle it manually.
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encryptedBytes = cipher.doFinal(paddedBytes);
// 4. Base64 Encode -> URL Encode
String base64String = Base64.getEncoder().encodeToString(encryptedBytes);
return URLEncoder.encode(base64String, StandardCharsets.UTF_8.toString());
}
public static String decrypt(String encryptedValue) throws Exception {
byte[] keyBytes = decodeBase64(KEY_B64, "KEY");
byte[] ivBytes = decodeBase64(IV_B64, "IV");
// 1. URL Decode -> Base64 Decode
String urlDecoded = URLDecoder.decode(encryptedValue, StandardCharsets.UTF_8.toString());
byte[] encryptedBytes = Base64.getDecoder().decode(urlDecoded);
// 2. Decrypt
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] decryptedPadded = cipher.doFinal(encryptedBytes);
// 3. Remove Padding (Manual)
int i = decryptedPadded.length - 1;
while (i >= 0 && decryptedPadded[i] == PADDING_VAL) {
i--;
}
byte[] decryptedBytes = Arrays.copyOf(decryptedPadded, i + 1);
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
public static void main(String[] args) {
try {
if (KEY_B64.equals("<KEY>")) {
throw new Exception("Please configure KEY_B64 and IV_B64 in the code.");
}
String msg = "Tatsuo|Kusakabe|[email protected]|0612345789|123456789";
System.out.println("\n--- TEST START ---");
System.out.println("Original: " + msg);
String encrypted = encrypt(msg);
System.out.println("Encrypted: " + encrypted);
String decrypted = decrypt(encrypted);
System.out.println("Decrypted: " + decrypted);
if (msg.equals(decrypted)) {
System.out.println("Status: ✅ SUCCESS - Decryption matches original");
} else {
System.out.println("Status: ❌ FAILED - Decryption mismatch");
}
System.out.println("--- TEST END ---\n");
} catch (Exception e) {
System.out.println("Status: ❌ ERROR - " + e.getMessage());
}
}
}# Note: Requires pycryptodome library (pip install pycryptodome).
import base64
import urllib.parse
import binascii
import sys
from Crypto.Cipher import AES
# Configuration (Base64 Strings)
KEY_B64 = "<KEY>"
IV_B64 = "<IV>"
# Custom Padding Character (0x1C)
PADDING_CHAR = b'\x1c'
BLOCK_SIZE = 16
def get_bytes(value, name="value"):
"""Helper to ensure we handle bytes, not strings, with validation."""
if isinstance(value, str):
try:
return base64.b64decode(value, validate=True)
except binascii.Error as e:
print(f"❌ CONFIG ERROR: The {name} provided is not a valid Base64 string.")
print(f" Details: {e}")
sys.exit(1)
return value
def add_padding(data_bytes):
# Calculate padding length to reach multiple of 16
padding_len = BLOCK_SIZE - (len(data_bytes) % BLOCK_SIZE)
# Append the custom character 0x1C repeatedly
return data_bytes + (PADDING_CHAR * padding_len)
def remove_padding(data_bytes):
# Remove the specific 0x1C padding character from the end
return data_bytes.rstrip(PADDING_CHAR)
def encrypt(text):
key = get_bytes(KEY_B64, "KEY")
iv = get_bytes(IV_B64, "IV")
# 1. Convert to bytes
data_bytes = text.encode('utf-8')
# 2. Add Custom Padding (CRITICAL STEP)
padded_data = add_padding(data_bytes)
# 3. Encrypt AES-CBC
cipher = AES.new(key, AES.MODE_CBC, iv)
encrypted_bytes = cipher.encrypt(padded_data)
# 4. Base64 -> URL Encode
return urllib.parse.quote_plus(base64.b64encode(encrypted_bytes).decode('utf-8'))
def decrypt(encrypted_url_string):
key = get_bytes(KEY_B64, "KEY")
iv = get_bytes(IV_B64, "IV")
# 1. URL Decode -> Base64 Decode
try:
encrypted_bytes = base64.b64decode(urllib.parse.unquote_plus(encrypted_url_string))
except binascii.Error:
raise ValueError("Input string is not valid Base64 data.")
# 2. Decrypt
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted_padded = cipher.decrypt(encrypted_bytes)
# 3. Remove Padding -> Decode to String
return remove_padding(decrypted_padded).decode('utf-8')
# Test
if __name__ == "__main__":
msg = "Tatsuo|Kusakabe|[email protected]|0612345789|123456789"
print("\n--- TEST START ---")
print(f"Original: {msg}")
try:
if KEY_B64 == "<KEY>":
raise ValueError("Please configure KEY_B64 and IV_B64 in the script.")
encrypted = encrypt(msg)
print(f"Encrypted: {encrypted}")
decrypted = decrypt(encrypted)
print(f"Decrypted: {decrypted}")
if msg == decrypted:
print("Status: ✅ SUCCESS - Decryption matches original")
else:
print("Status: ❌ FAILED - Decryption mismatch")
except Exception as e:
print(f"Status: ❌ ERROR - {e}")
print("--- TEST END ---\n")Updated 3 days ago