Skip to content

Instantly share code, notes, and snippets.

@cryptolos
Last active September 21, 2023 10:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save cryptolos/76c07359b73c3b7f9eea25a6e22db620 to your computer and use it in GitHub Desktop.
Save cryptolos/76c07359b73c3b7f9eea25a6e22db620 to your computer and use it in GitHub Desktop.
// --------- Encryption key generation --------------------------------
// This generates an encryption key that can be used to encrypt data
// and converts it into a base64 string to make it easy to store
cryptoKey = generateAes256Key();
// Base64 encoded version of the key which is easier to
// store and share between systems
cryptoKey64 = base64Encode(cryptoKey);
// --------- SSO string generation ------------------------------------
// This combines contextual information needed for the app
// and encodes into a secure sso code
// Generate the data to be encrypted.
orgId = '';
subjectId = 'XXXXXXXXXXXXXXX'; // This is the contact Id
expiresAt = 9999999999;
// Concatenate the values in a manner that is easy
// to decompose it (split at the dot)
compositeString = orgId + '.' + subjectId + '.' + expiresAt;
// Generate initialization vector
iv = generateRandom16Bytes();
// convert to blob
data = Blob(compositeString);
// Encrypt the data with the initialization vector and the encryption key
encryptedData = encryptToAes256WithIV(cryptoKey, iv, data);
// Base64 Encode the data to make it usable as a URL parameter
encodedData = base64Encode(encryptedData);
// Base64 includes charaters that conflict with reserved URL characters therefore
// additional transformation needs to be performed to replace reserved URL characters
// with URL friendly ones (remove '=', replace '/' with '_', replace '+' with '-')
ssoCode = encodedData.replace('=', '').replace('/', '_').replace('+', '-');
// -------- SSO string decomposition -----------------------------------
// This will be the logic to be performed on the paylink app to retrieve
// context information to be displayed to the user. It is provided here
// as a means of testing and validating the composition method
// Reverse the process to get the original data
// Revert the base64 characters that were replaced, the equal '=' sign in
// base64 is used as a padding character is completely optional, therefore
// no need to get it back
base64String = ssoCode.replace('[_]', '/').replace('[-]', '+');
// Turn it back into the raw encrypted data
decodedData = base64Decode(base64String);
// Decrypt the data - the first 16 bytes contain the initialization vector
decryptedData = decryptFromAes256WithIV(cryptoKey, decodedData);
// Decode the decrypted data buffer to string for subsequent use
ecryptedDataString = decryptedData.toString();
@therajumandapati
Copy link

therajumandapati commented Mar 2, 2021

PHP version of the above code see here:

$aes_key = random_bytes(32);

$aes_key_base64 = base64_encode($aes_key);

$orgId = '';
$subjectId = '456789'; // This is the contact Id
$expiresAt = 9999999999;

$compositeString = join(',', [$orgId, $subjectId, $expiresAt]);

$iv = openssl_random_pseudo_bytes(16);

$data = $compositeString;

$encryptedData = openssl_encrypt($data, 'aes-256-cbc', $aes_key_base64, OPENSSL_RAW_DATA, $iv);

$encodedData = base64_encode($iv.$encryptedData);

$ssoCode = str_replace(['/', '+', '='], ['_', '-', ''], $encodedData);

// Decompose
$decodedData = str_replace('-', '+', $ssoCode);
$decodedData = str_replace('_', '/', $decodedData);

print_r("\nDecoded Data: ");
print_r($decodedData);

$decodedData = base64_decode($decodedData);

$decryptedData = openssl_decrypt($decodedData, 'aes-256-cbc', $aes_key_base64, OPENSSL_RAW_DATA, $iv);

@crysislinux
Copy link

crysislinux commented Sep 27, 2022

There are two issues in the above sample PHP code:

  1. should use . instead of , to separate the parts
  2. should use $aes_key instead of $aes_key_base64 to encrypt.

Here is an updated version:

<?php

// 256-bit key (32 bytes)
$aes_key = "P4wj4zUQ3FIxJPu3Z2KPtI8PQquVLk4M";

print_r($aes_key);
print_r("\n");

$aes_key_base64 = base64_encode($aes_key);

$orgId = '';
$subjectId = '456789'; // This is the contact Id
$expiresAt = 9999999999; // this is a timestamp in the future

$compositeString = join('.', [$orgId, $subjectId, $expiresAt]);

$iv = openssl_random_pseudo_bytes(16);

$data = $compositeString;

$encryptedData = openssl_encrypt($data, 'aes-256-cbc', $aes_key, OPENSSL_RAW_DATA, $iv);

$encodedData = base64_encode($iv.$encryptedData);

$ssoCode = str_replace(['/', '+', '='], ['_', '-', ''], $encodedData);

print_r($ssoCode);
print_r("\n");

@crysislinux
Copy link

Here is a typescript version

import * as crypto from 'crypto';

export interface DecodedContext {
  /**
   * Optional org ID
   */
  orgId?: string;
  /**
   * Record ID of root context object
   */
  subjectId: string;
  /**
   * Optional code expiration as number of seconds since the UNIX epoch
   */
  expiresAt?: number;
}

export const encodeContext = (context: DecodedContext, secretKey: Buffer): string => {
  const iv = crypto.randomBytes(16);
  const plaintext = Buffer.from([ context.orgId, context.subjectId, context.expiresAt ].join('.'), 'utf8');
  const cipher = crypto.createCipheriv('aes256', secretKey, iv);
  const encrypted = Buffer.concat([iv,  cipher.update(plaintext), cipher.final() ]);

  return encrypted.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
};


export const createSSOCode = (contactId: string, expiresIn: number, SSO_ENCRYPTION_KEY: Buffer): string => {
  return encodeContext({
    orgId: '', // not setting an org id to shorten the context code
    subjectId: contactId,
    expiresAt: Math.floor(Date.now() / 1000) + expiresIn
  }, SSO_ENCRYPTION_KEY);
};

const run = () => {
  // 256-bit key (32 bytes)
  const encryptionKey = "P4wj4zUQ3FIxJPu3Z2KPtI8PQquVLk4M";
  const subjectId = '456789'; // This is the contact Id
  const expiresIn = 1 * 60 * 24 // expire after 24 hours

  const ssoCode = createSSOCode(subjectId, expiresIn, Buffer.from(encryptionKey));
  console.log(ssoCode);
}

run()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment