-
-
Save cryptolos/76c07359b73c3b7f9eea25a6e22db620 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// --------- 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(); |
There are two issues in the above sample PHP code:
- should use
.
instead of,
to separate the parts - 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");
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
PHP version of the above code see here: