Quick Quotes via Encrypted URLs (Homelyfe)

📘

Credentials Required

In order to use the Quick Quotes via Encrypted URLs functionality, you will require a Partner ID and encryption keys provided by the Aventus Platform. Please contact Homelyfe to obtain these.

Quick Quotes are used to provide an indicative price of insurance by making certain assumptions about risk data in order to provide users with a price indication as quickly and as easily as possible.

Quick Quotes via Encrypted URLs enable integrators to share data in a secure way with the Homelyfe app by encrypting the confidential customer data. Customer data sent in the request payload is encrypted using 256-bit AES encryption then hashed using the [Encrypt-then-MAC][1] method.
[1]: https://en.wikipedia.org/wiki/Authenticated_encryption#Encrypt-then-MAC_(EtM)

Updates

DateDescription
20 September 2019Updated to include the new renters lineOfBusiness and coverType options.

URL & Parameter Reference

GET https://homelyfe-test.aventus.app/widgets?partnerId=537584-8453-13K9-H2L7-4EB21A35723D&widget=vri&policyType=2&lineOfBusiness=home&coverType=homebuildingsandcontents&payload=mXt%2B9dGttxAx442woVGe%2FaQXv5hm%2F143ebP75gussaXUEGOMiEqmVC3M1%2FFvxM%2F0wdJ1h2FeF5VYGF2a1ODKy%2Flcf0YtiyLgPWooC9f9jm6W7v4z4taVjgUr%2BDvh0CzA2IDhHf76YVD8%2F%2FFpFs4ME%2F1tjcyd5a0RwY0yoanaeCtQc5seKKHsXBSZXnL17DMCzeQOP9zItW9dXWgKuXMzBLVHR%2BgXEe52miO1sjFD15UYGl6oqs6knMAszyp67Gt6F04eINaFgtS%2FB0Ed1LcNPygznrn0mw3L0sQjwHnGak%2FZFm90DV0PCSeDJtZwAs7sdzsJuwWsB5X3UzWKT%2FBOgqzzuII4mqSLR8iZhvkUqbowiSAJ8J8wxmn%2BTxCJ3CVjpllqCR7Y5xsZAKbq3xWR3Q%3D%3D

Homelyfe Environments

Parameters

NameDataTypeDescription
partnerIdUUID (guid)
Required
Your partner ID
widgetstring
Required
The Homelyfe service you're accessing.


vri
lineOfBusinessstring
Required
The type of insurance you're requesting.


home
renters
coverTypestring
Required
The specific insurance product.


home lineOfBusiness:
- homebuildingsandcontents
- homebuildings
- homecontents


renters lineOfBusiness:
- homecontents
psstring
Optional
The payment schedule, whether annual or monthly.
Omit this parameter if you want to enable the option to toggle between both.

- a (annual)
- m (monthly)
payloadstring
Required
Encrypted JSON data payload.

See below for instructions of how to generate the payload

Payload reference

Payload is encrypted using 256-bit AES encryption then hashed using the [Encrypt-then-MAC][1] method to ensure users data remains safe & secure.
[1]: https://en.wikipedia.org/wiki/Authenticated_encryption#Encrypt-then-MAC_(EtM)

The following is a reference of the JSON fields that are included in the encrypted payload.

NameDataTypeDescription
firstNamestring
Required
First name of customer
lastNamestring
Required
Last name of customer
dateOfBirthdate
Required
Date of birth of customer, formatted YYYY-MM-DD
emailAddressstring
Required
Customers email address
phoneNumberstring
Required
Phone number of customer as an unformatted string
address1string
Required
First line of address, usually house or flat number
address2string
Optional
Second address line
townstring
Optional
Town/Region
postalCodestring
Required
UK Postcode
partnerReferencestring
Optional
Your own unique reference ID for the customer.

When a policy is purchased we’ll include the partnerReference in the Policy Purchased webhook allowing you to link the policy purchase to the appropriate customer in your system.
noncestring
Required
A random, unique string per request. This ensures that we process requests once, and is used to prevent replay attacks.
timestampdatetime
Required
The datetime in UTC when the payload was generated.

The timestamp needs to be formatted using ISO 8601 Date formatting (e.g. 2018-10-08T11:18:57Z)

Generating the Encrypted Payload

You'll be provided with two keys, an encryption key to encrypt the payload and an authentication key for the MAC part. The keys will be provided in a base64-encoded format, your application will need to base64-decode the keys before using them.

🚧

You'll receive different partner IDs and secrets for each environment, this is for data safety reasons. Be sure not to mix these, and to double check you've entered the correct details.

Here's a step-by-step guide to creating the encrypted payload.

1. Construct the JSON payload containing the user's details

{
	"firstName" : "",
	"lastName" : "",
	"dateOfBirth"  : "",
	"emailAddress" : "",
	"phoneNumber" : "",
	"address1" : "",
	"address2" : "",
	"town" : "",
	"postalCode" : "",
	"partnerReference" : "",
	"nonce" : "",
	"timestamp" : "" 
}

The following fields are mandatory:

  • firstName
  • lastName
  • dateOfBirth
  • emailAddress
  • phoneNumber
  • address1
  • postalCode
  • nonce
  • timestamp

2. Encrypt the JSON payload using AES

2.1 Use the following AES configuration:

  • Key Size: 256 bits
  • Block Size: 128 bits
  • Cipher Mode: CBC
  • Padding Mode: PKCS7

2.2 Generate a random Initialization Vector (IV) of size 16 bytes

2.3 Encrypt the JSON payload using your encryption key and the Initialization Vector to generate the cipher

3. Sign the cipher using HMAC-SHA256 and your authentication key

3.1 Concatenate the IV and the cipher (IV + cipher)

3.2 Compute a hash of the (IV + cipher)
hash = HMAC-SHA256(IV + cipher)

3.3 Concatenate the hash to the (IV + cipher)
signed-payload = (IV + cipher + hash)

4. Base64-encode the (IV + cipher + hash)

base64-payload = base64(signed-payload)

5. URL encode the base64 string

payload = urlencode(base64-payload)

Step-by-step example

The step-by-step example below shows the necessary steps in encrypting, signing & formatting the payload with the expected outcome at each step (base64-encoded)

This example uses the following keys:

Encryption keyDbU0fQQc5IbF4v9a/KUvMrzF2EWGhnp7Ussb4GvWk9M=
Authentication keyHPqKD+TDmsZPuKSZHSmpZT+Y2nGsZ7uijy1NesJWbyU=
Random 16 bytes Initialisation Vector (IV)6AimI+hLvyAuxLlTlGN0pg==
StepDescriptionOutcome
1Construct the JSON payload{"firstName":"John","lastName":"Doe","dateOfBirth":"1970-01-01T00:00:00Z","emailAddress":"[email protected]","phoneNumber":"07000123456","address1": "1 Anchor Road","address2":"Kingsclere","town":"Newbury","postalCode":"RG20 5NE","partnerReference":"01F0B37A-E056-4415-93D9-03F75506EE17","nonce":"u_KDhf2j79jCjDZ-ArFqHXOKqaC.B9sZ","timestamp","2018-10-19T11:35:20Z"}
2Encrypt the JSON with encryption key + IVFYOX4sfwwshBs9sxACOP8W2Fez9shCP4eYUB0oTJf7/KbD5vh92G3Y/f/u3rzebHRO60guu9qY17Z+vnQMREZPR0EzzGa5yyzvD9UuHoPR/MiiLyLcAHphAts6QWhpuosfY8mCoSd9p+Sp365LZkjXAIa/JWSMVa981Ykriedr7Pz2bLtsbkc0JF7M2Yq483mOIZtCKq997UsAEulXNQ2y75hpNu+g1898/65YThcd7YDJ8d/0QQ/69JRgTFPtVak4EyIhUTUViW7FhuifYDw4sjhmlBxclyy/zsRGdQrG13GAtDKdgl2Upg/QKWsMiNKhSnKtQ/cDDjJqs8y+MQ/XrKPrNEpSor4ltLIC1b/Sj5Z/0I+DPh0F3Z9wcqBObjVOjN7mFHsQDcZRLG6xWt2V9QqhFF7Ka6Gi0f6BXVlWQx8X2umOjrDD+M+8t8i/aVMGkYQiag0bUkdY77qXjvVCe4Ke/dpGyJZyuHy7YC19o=
3.1Concatenate IV + cipher6AimI+hLvyAuxLlTlGN0phWDl+LH8MLIQbPbMQAjj/FthXs/bIQj+HmFAdKEyX+/ymw+b4fdht2P3/7t683mx0TutILrvamNe2fr50DERGT0dBM8xmucss7w/VLh6D0fzIoi8i3AB6YQLbOkFoabqLH2PJgqEnfafkqd+uS2ZI1wCGvyVkjFWvfNWJK4nna+z89my7bG5HNCRezNmKuPN5jiGbQiqvfe1LABLpVzUNsu+YaTbvoNfPfP+uWE4XHe2AyfHf9EEP+vSUYExT7VWpOBMiIVE1FYluxYbon2A8OLI4ZpQcXJcsv87ERnUKxtdxgLQynYJdlKYP0ClrDIjSoUpyrUP3Aw4yarPMvjEP16yj6zRKUqK+JbSyAtW/0o+Wf9CPgz4dBd2fcHKgTm41Toze5hR7EA3GUSxusVrdlfUKoRReymuhotH+gV1ZVkMfF9rpjo6ww/jPvLfIv2lTBpGEImoNG1JHWO+6l471QnuCnv3aRsiWcrh8u2Atfa
3.2Compute hashcTwKkvrFuYjZHEpCtAWocg9+1KslEka0zIbw+K0D6Wc=
3.3Concatenate IV + cipher + hash6AimI+hLvyAuxLlTlGN0phWDl+LH8MLIQbPbMQAjj/FthXs/bIQj+HmFAdKEyX+/ymw+b4fdht2P3/7t683mx0TutILrvamNe2fr50DERGT0dBM8xmucss7w/VLh6D0fzIoi8i3AB6YQLbOkFoabqLH2PJgqEnfafkqd+uS2ZI1wCGvyVkjFWvfNWJK4nna+z89my7bG5HNCRezNmKuPN5jiGbQiqvfe1LABLpVzUNsu+YaTbvoNfPfP+uWE4XHe2AyfHf9EEP+vSUYExT7VWpOBMiIVE1FYluxYbon2A8OLI4ZpQcXJcsv87ERnUKxtdxgLQynYJdlKYP0ClrDIjSoUpyrUP3Aw4yarPMvjEP16yj6zRKUqK+JbSyAtW/0o+Wf9CPgz4dBd2fcHKgTm41Toze5hR7EA3GUSxusVrdlfUKoRReymuhotH+gV1ZVkMfF9rpjo6ww/jPvLfIv2lTBpGEImoNG1JHWO+6l471QnuCnv3aRsiWcrh8u2AtfacTwKkvrFuYjZHEpCtAWocg9+1KslEka0zIbw+K0D6Wc=
5URL encode6AimI%2bhLvyAuxLlTlGN0phWDl%2bLH8MLIQbPbMQAjj%2fFthXs%2fbIQj%2bHmFAdKEyX%2b%2fymw%2bb4fdht2P3%2f7t683mx0TutILrvamNe2fr50DERGT0dBM8xmucss7w%2fVLh6D0fzIoi8i3AB6YQLbOkFoabqLH2PJgqEnfafkqd%2buS2ZI1wCGvyVkjFWvfNWJK4nna%2bz89my7bG5HNCRezNmKuPN5jiGbQiqvfe1LABLpVzUNsu%2bYaTbvoNfPfP%2buWE4XHe2AyfHf9EEP%2bvSUYExT7VWpOBMiIVE1FYluxYbon2A8OLI4ZpQcXJcsv87ERnUKxtdxgLQynYJdlKYP0ClrDIjSoUpyrUP3Aw4yarPMvjEP16yj6zRKUqK%2bJbSyAtW%2f0o%2bWf9CPgz4dBd2fcHKgTm41Toze5hR7EA3GUSxusVrdlfUKoRReymuhotH%2bgV1ZVkMfF9rpjo6ww%2fjPvLfIv2lTBpGEImoNG1JHWO%2b6l471QnuCnv3aRsiWcrh8u2AtfacTwKkvrFuYjZHEpCtAWocg9%2b1KslEka0zIbw%2bK0D6Wc%3d

Example code

const int keySize = 256;
const int blockSize = 128;
const CipherMode cipherMode = CipherMode.CBC;
const PaddingMode paddingMode = PaddingMode.PKCS7;
const int ivLength = 16;
static Random random = new Random();

static void Main(string[] args) 
{
  var nonce = GenerateNonce();
  var utcNow = DateTime.UtcNow.ToString("o");

  var input = $@"{{""firstName"":""John"",""lastName"":""Doe"",""dateOfBirth"":""1970-01-01T00:00:00Z"",""emailAddress"":""[email protected]"",""phoneNumber"":""07000123456"",""address1"":""1 Anchor Road"",""address2"":""Kingsclere"",""town"":""Newbury"",""postalCode"":""RG20 5NE"",""partnerReference"":""01F0B37A-E056-4415-93D9-03F75506EE17"",""nonce"":""{nonce}"",""timestamp"":""{utcNow}""}}";
  
  var encryptionKey = Convert.FromBase64String("DbU0fQQc5IbF4v9a/KUvMrzF2EWGhnp7Ussb4GvWk9M=");
  var authenticationKey = Convert.FromBase64String("HPqKD+TDmsZPuKSZHSmpZT+Y2nGsZ7uijy1NesJWbyU=");
  
  string encryptedText = Encrypt(input, encryptionKey, authenticationKey);
  string decryptedText = Decrypt(encryptedText, encryptionKey, authenticationKey);
}

public static string Encrypt(string input, byte[] encryptionKey, byte[] authenticationKey) 
{
  var inputBytes = Encoding.UTF8.GetBytes(input);
  
  byte[] cipher;
  
  using (var aes = new AesManaged())
  {
    aes.KeySize = keySize;
    aes.BlockSize = blockSize;
    aes.Mode = cipherMode;
    aes.Padding = paddingMode;
    aes.Key = encryptionKey;
    aes.GenerateIV();
    
    using (MemoryStream ms = new MemoryStream())
    {
      ms.Write(aes.IV, 0, aes.IV.Length);
      using (CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write))
      {
        cs.Write(inputBytes, 0, inputBytes.Length);
        cs.FlushFinalBlock();
      }
      
      cipher = ms.ToArray();
    }
  }
  
  string base64SignedPayload;
  using (HMACSHA256 hmac = new HMACSHA256(authenticationKey))
  {
    using (MemoryStream ms = new MemoryStream())
    {
      ms.Write(cipher, 0, cipher.Length);
      var hash = hmac.ComputeHash(cipher);
      ms.Write(hash, 0, hash.Length);
      
      base64SignedPayload = Convert.ToBase64String(ms.ToArray());
    }
  }
  
  return HttpUtility.UrlEncode(base64SignedPayload);
}

public static string Decrypt(string encryptedText, byte[] encryptionKey, byte[] authenticationKey)
{
  var base64SignedPayload = HttpUtility.UrlDecode(encryptedText);
  var signedPayload = Convert.FromBase64String(base64SignedPayload);
  
  byte[] cipher;
  
  using (HMACSHA256 hmac = new HMACSHA256(authenticationKey))
  {
    var storedHash = new byte[hmac.HashSize / 8];
    var cipherLength = signedPayload.Length - storedHash.Length;
    
    cipher = new byte[cipherLength];
    
    using (MemoryStream ms = new MemoryStream(signedPayload))
    {
      ms.Read(cipher, 0, cipherLength);
      ms.Read(storedHash, 0, storedHash.Length);
      
      var computedHash = hmac.ComputeHash(cipher);
      
      if (!computedHash.SequenceEqual(storedHash))
      {
        return null;
      }
    }
  }
  
  using (AesManaged aes = new AesManaged())
  {
    aes.KeySize = keySize;
    aes.BlockSize = blockSize;
    aes.Mode = cipherMode;
    aes.Padding = paddingMode;
    aes.Key = encryptionKey;
    
    var iv = new byte[ivLength];
    var encrypted = new byte[cipher.Length - ivLength];
    
    using (MemoryStream ms = new MemoryStream(cipher))
    {
      ms.Read(iv, 0, ivLength);
      aes.IV = iv;
      ms.Read(encrypted, 0, encrypted.Length);
      
      using (MemoryStream decrypted = new MemoryStream())
      {
        using (CryptoStream cs = new CryptoStream(decrypted, aes.CreateDecryptor(), CryptoStreamMode.Write))
        {
          cs.Write(encrypted, 0, encrypted.Length);
        }
        
        return Encoding.UTF8.GetString(decrypted.ToArray());
      }
    }
  }
  
  private static string GenerateNonce()
  {
    var nonceLength = 32;
    var validChars =  "0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._~";
    
    var nonceString = new StringBuilder();
    
    using (var rnd = new RNGCryptoServiceProvider())
    {
      while (nonceString.Length < nonceLength)
      {
        var bytes = new byte[1];
        rnd.GetBytes(bytes);
        var character = (char)bytes[0];
        if (validChars.Contains(character))
        {
          nonceString.Append(character);
        }
      }
    }
    return nonceString.ToString();
  }
}
var CryptoJS = require('crypto-js');
var _ = require('lodash');

var encryptionKey = CryptoJS.enc.Base64.parse('DbU0fQQc5IbF4v9a/KUvMrzF2EWGhnp7Ussb4GvWk9M=');
var authenticationKey = CryptoJS.enc.Base64.parse('HPqKD+TDmsZPuKSZHSmpZT+Y2nGsZ7uijy1NesJWbyU=');
var ivLength = 128 / 8;
var nonce = generateNonce(32);
var utcNow = new Date().toISOString();

var json = {
  "firstName": "John",
  "lastName": "Doe",
  "dateOfBirth": "1970-01-01T00:00:00Z",
  "emailAddress": "[email protected]",
  "phoneNumber": "07000123456",
  "address1": "1 Anchor Road",
  "address2": "Kingsclere",
  "town": "Newbury",
  "postalCode": "RG20 5NE",
  "partnerReference": "01F0B37A-E056-4415-93D9-03F75506EE17",
  "nonce": nonce,
  "timestamp": utcNow
}

var payload = JSON.stringify(json);

var encryptedText = encrypt(payload, encryptionKey, authenticationKey);

var decryptedText = decrypt(encryptedText, encryptionKey, authenticationKey);

function encrypt(input, encryptionKey, authenticationKey) {
  var iv = CryptoJS.lib.WordArray.random(ivLength);
  var encrypted = CryptoJS.AES.encrypt(payload, encryptionKey, {
    iv: iv,
    padding: CryptoJS.pad.Pkcs7,
    mode: CryptoJS.mode.CBC
  });
  
  var hashData = iv.concat(encrypted.ciphertext);
  
  var hash = CryptoJS.HmacSHA256(hashData, authenticationKey);
  
  var signedPayload = hashData.concat(hash);
  
  var base64SignedPayload = CryptoJS.enc.Base64.stringify(signedPayload);
  
  return encodeURIComponent(base64SignedPayload);
}

function decrypt(encryptedText, encryptionKey, authenticationKey) {
  var base64SignedPayload = decodeURIComponent(encryptedText);
  var signedPayload = CryptoJS.enc.Base64.parse(base64SignedPayload);
  
  //CryptoJS WordArray is 4 bytes in a single value
  var wordArray = 4;
  var hashLength = 256 / 8; // bytes
  
  var cipherlength = signedPayload.words.length - (hashLength / wordArray);
  
  // Split the signedPayload into two arrays - cipher & hash
  var cipher = signedPayload.clone();
  cipher.sigBytes = cipherlength * wordArray;
  cipher.clamp();
  
  var hash = signedPayload.clone();
  hash.words.splice(0, cipherlength);
  hash.sigBytes -= cipherlength * wordArray;
  
  // compute the hash and compare to what was sent
  var computedHash = CryptoJS.HmacSHA256(cipher, authenticationKey);
  var out = _.difference(computedHash.words, hash.words);
  
  if (out.length != 0) {
    console.log("incorrect hash");
    return;
  }
  
  // split the cipher into the two arrays - iv & the encrypted message
  var iv = cipher.clone();
  iv.sigBytes = (ivLength);
  iv.clamp();
  
  var encrypted = cipher.clone();
  encrypted.words.splice(0, wordArray);
  encrypted.sigBytes -= ivLength;
  
  var decrypted = CryptoJS.AES.decrypt({
    ciphertext: encrypted
  }, encryptionKey, {
    iv: iv,
    padding: CryptoJS.pad.Pkcs7,
    mode: CryptoJS.mode.CBC
  });
  return decrypted.toString(CryptoJS.enc.Utf8);
}

function generateNonce(length) {
  var bytes = new Uint8Array(length);
  var random = window.crypto.getRandomValues(bytes);
  var result = [];
  var charset = '0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._~';
  random.forEach(function (c) {
    result.push(charset[c % charset.length]);
  });
  return result.join('');
}