Gateway API
Security Trailers
19 min
every message sent to the toro gateway must include a valid security trailer for detailed guidance on the format of the security trailer, please see the nexo card payment protocols security message user guide (mug) which can be downloaded from https //nexo standards org the toro gateway requires implementers to provide a security trailer using the authenticated data type; in other words, your implementation should generate a mac (message authentication code) using one of the supported algorithms and specify this in the security trailer the security trailer protects the message using a mac (message authentication code) mac generation and mac verification use the same algorithms mac is verified by generating the mac from the received message, and compared to the received mac at a high level, the steps to generate a security trailer are during onboarding, a shared passphrase is generated and provided key material is derived from the shared passphrase using pbkdf2 a mac is calculated for a given message body using the derived key material a security trailer is generated, setting the mac field and other information an example security trailer is shown below \<sctytrlr> 	\<cntttp>auth\</cntttp> 	\<authntcddata> 	 \<rcpt> 	 \<keyidr> 	 \<keyid>specv1testkey\</keyid> 	 \<keyvrsn>1\</keyvrsn> 	 \</keyidr> 	 \</rcpt> 	 \<macalgo> 	 \<algo>cca3\</algo> 	 \</macalgo> 	 \<ncpsltdcntt> 	 \<cntttp>data\</cntttp> 	 \</ncpsltdcntt> 	 \<mac>9xiobnj/pyab10eb4l6n4w==\</mac> 	\</authntcddata> \</sctytrlr> derive key material during onboarding to the toro platform, you will be provided with a passphrase which can be used to derive the relevant cryptographic material needed to generate and verify message security trailers draft documentation the fixed passphrase shown below is only for use while the system is in beta while the system is in beta, the following passphrase can be used by all integrating parties torogatewaynexov1passphrasedevonly the passphrase is used in combination with a fixed salt to generate a 32 byte (256 bit) mac key the process uses pbkdf2 (password based key derivation function 2) to generate cryptographic keys and an initialization vector (iv) from a single passphrase key material is derived from the shared passphrase, and only changes when the shared key changes therefore it is not necessary to derive key material for each message however, the derived key material is secret, so if your implementation does not derive key material for every message, it is important to store the key material securely in your system required parameters passphrase a string containing the secret passphrase salt a string used as cryptographic salt the salt value is fixed and should always be torogatewaynexov1salt fixed parameters iterations 4000 output length 80 bytes hash function sha 256 derived output the pbkdf2 function will generate an 80 byte output this should be used as follows cmac key bytes 1 32 cipher key bytes 33 64 initialisation vector bytes 65 80 code examples openssl kdf keylen 80 kdfopt digest\ sha256 kdfopt iter 4000 kdfopt salt "torogatewaynexov1salt" kdfopt pass "torogatewaynexov1passphrasedevonly" pbkdf2 | tr d ' \n' > keymaterial hex \# expected contents of file keymaterial hex \# e05dcba1276a3d822f38c7a6c45f2fcfd4be8b0849588633f5cd9d210244ee8ff4d7dfe8f8e2f16ffd87adf470dbef42191986af143f45c7f70046c05daae7fa50df6be87422b9ec129ecd1b67548485 \# extract cmac key (first 64 hex chars = 32 bytes) echo "cmac key " cut c 1 64 keymaterial hex \# expected e05dcba1276a3d822f38c7a6c45f2fcfd4be8b0849588633f5cd9d210244ee8f \# extract cipher key (next 64 hex chars = 32 bytes) echo e "\ncipher key " cut c 65 128 keymaterial hex \# expected f4d7dfe8f8e2f16ffd87adf470dbef42191986af143f45c7f70046c05daae7fa \# extract iv (last 32 hex chars = 16 bytes) echo e "\niv " cut c 129 160 keymaterial hex \# expected 50df6be87422b9ec129ecd1b67548485function nexoderivekeymaterial($passphrase) { $outlen = 80; $salt = "torogatewaynexov1salt"; $rounds = 4000; $bytes = openssl pbkdf2($passphrase, $salt, $outlen, $rounds, "sha256"); $cmac key = substr($bytes, 0, 32); $cipher key = substr($bytes, 32, 32); $iv = substr($bytes, 64, 16); return array('cmac key' => $cmac key, 'cipher key' => $cipher key, 'iv' => $iv); }import hashlib from cryptography hazmat primitives import hashes from cryptography hazmat primitives kdf pbkdf2 import pbkdf2hmac def nexo derive keymaterial(passphrase str) > dict salt = b"torogatewaynexov1salt" rounds = 4000 outlen = 80 kdf = pbkdf2hmac( algorithm=hashes sha256(), length=outlen, salt=salt, iterations=rounds, ) derived = kdf derive(passphrase encode('utf 8')) return { 'cmac key' derived\[0 32], 'cipher key' derived\[32 64], 'iv' derived\[64 80] }using system security cryptography; public class keyderivation { public static dictionary\<string, byte\[]> nexoderivekeymaterial(string passphrase) { byte\[] salt = system text encoding utf8 getbytes("torogatewaynexov1salt"); int rounds = 4000; int outlen = 80; using (var pbkdf2 = new rfc2898derivebytes( passphrase, salt, rounds, hashalgorithmname sha256)) { byte\[] derived = pbkdf2 getbytes(outlen); return new dictionary\<string, byte\[]> { { "cmac key", derived take(32) toarray() }, { "cipher key", derived skip(32) take(32) toarray() }, { "iv", derived skip(64) take(16) toarray() } }; } } }# java implementation import javax crypto secretkeyfactory; import javax crypto spec pbekeyspec; import java security spec keyspec; public class keyderivation { public static map\<string, byte\[]> nexoderivekeymaterial(string passphrase) throws exception { byte\[] salt = "torogatewaynexov1salt" getbytes(standardcharsets utf 8); int rounds = 4000; int outlen = 80; keyspec spec = new pbekeyspec( passphrase tochararray(), salt, rounds, outlen 8 // keyspec expects bits, not bytes ); secretkeyfactory factory = secretkeyfactory getinstance("pbkdf2withhmacsha256"); byte\[] derived = factory generatesecret(spec) getencoded(); map\<string, byte\[]> result = new hashmap<>(); result put("cmac key", arrays copyofrange(derived, 0, 32)); result put("cipher key", arrays copyofrange(derived, 32, 64)); result put("iv", arrays copyofrange(derived, 64, 80)); return result; } }require 'openssl' def nexo derive keymaterial(passphrase) salt = "torogatewaynexov1salt" rounds = 4000 outlen = 80 derived = openssl pkcs5 pbkdf2 hmac( passphrase, salt, rounds, outlen, openssl digest sha256 new ) { cmac key derived byteslice(0, 32), cipher key derived byteslice(32, 32), iv derived byteslice(64, 16) } endpackage main import ( "golang org/x/crypto/pbkdf2" "crypto/sha256" ) func nexoderivekeymaterial(passphrase string) map\[string]\[]byte { salt = \[]byte("torogatewaynexov1salt") rounds = 4000 outlen = 80 derived = pbkdf2 key( \[]byte(passphrase), salt, rounds, outlen, sha256 new, ) return map\[string]\[]byte{ "cmac key" derived\[0 32], "cipher key" derived\[32 64], "iv" derived\[64 80], } }const crypto = require('crypto'); function nexoderivekeymaterial(passphrase) { const salt = "torogatewaynexov1salt"; const rounds = 4000; const outlen = 80; const derived = crypto pbkdf2sync( passphrase, salt, rounds, outlen, 'sha256' ); return { cmac key derived subarray(0, 32), cipher key derived subarray(32, 64), iv derived subarray(64, 80) }; }import javax crypto secretkeyfactory import javax crypto spec pbekeyspec import java nio charset standardcharsets fun nexoderivekeymaterial(passphrase string) map\<string, bytearray> { val salt = "torogatewaynexov1salt" tobytearray(standardcharsets utf 8) val rounds = 4000 val outlen = 80 val spec = pbekeyspec( passphrase tochararray(), salt, rounds, outlen 8 // keyspec expects bits, not bytes ) val factory = secretkeyfactory getinstance("pbkdf2withhmacsha256") val derived = factory generatesecret(spec) encoded return mapof( "cmac key" to derived copyofrange(0, 32), "cipher key" to derived copyofrange(32, 64), "iv" to derived copyofrange(64, 80) ) }import foundation import commoncrypto func nexoderivekeymaterial(passphrase string) > \[string data] { let salt = "torogatewaynexov1salt" data(using utf8)! let rounds = 4000 let outlen = 80 var derivedkeydata = data(repeating 0, count outlen) // derive the key using pbkdf2 let result = derivedkeydata withunsafemutablebytes { derivedkeybytes in salt withunsafebytes { saltbytes in cckeyderivationpbkdf( ccpbkdfalgorithm(kccpbkdf2), // algorithm passphrase, // password passphrase lengthofbytes(using utf8), // passwordlen saltbytes baseaddress? assumingmemorybound( // salt to uint8 self), salt count, // saltlen ccpbkdfalgorithm(kccprfhmacalgsha256), // prf uint32(rounds), // rounds derivedkeybytes baseaddress? assumingmemorybound( to uint8 self), // derivedkey outlen // derivedkeylen ) } } guard result == kccsuccess else { fatalerror("pbkdf2 computation failed") } return \[ "cmac key" derivedkeydata subdata(in 0 <32), "cipher key" derivedkeydata subdata(in 32 <64), "iv" derivedkeydata subdata(in 64 <80) ] } implementation verification to verify that your implementation is working as expected, please ensure that the following inputs result in the output shown salt torogatewaynexov1salt passphrase torogatewaynexov1passphrasedevonly mac key e0 5d cb a1 27 6a 3d 82 2f 38 c7 a6 c4 5f 2f cf d4 be 8b 08 49 58 86 33 f5 cd 9d 21 02 44 ee 8f cipher key f4 d7 df e8 f8 e2 f1 6f fd 87 ad f4 70 db ef 42 19 19 86 af 14 3f 45 c7 f7 00 46 c0 5d aa e7 fa initialisation vector (iv) 50 df 6b e8 74 22 b9 ec 12 9e cd 1b 67 54 84 85 security trailer structure a valid security trailer should be sent with every request it should conform to the defined nexo standard and use the authenticatedmessage data structure the relevant fields for iso 20022 message component contentinformationtype36 are shown below true 0,0,0,0,109 unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type unhandled content type mac generation for further information on mac generation for nexo messages, please see the nexo protocol security mug section 5 mac generation and mac verification use the same algorithms mac is verified by generating the mac from the received message, and compared to the received mac in response messages, the gateway will generate a security trailer using the same mac algorithm that was used in the request message mac computation is carried out on the message body only the header (and, obviously, security trailer) components of a message should not be included in the mac computation input it is important that the mac computation is carried out on the minified version of the message body, omitting all unnecessary spaces and carriage returns this ensures that calculation and verification can be carried out using consistent representations of the message body supported mac algorithms the toro gateway supports the following mac algorithms cmac with aes128 sha 256 cmac with aes128 sha3 256 cmac with aes128 cmac with aes192 sha 384 cmac with aes192 sha3 384 cmac with aes192 cmac with aes256 sha 512 cmac with aes256 sha3 512 cmac with aes256 mac generation examples the following examples are adapted from the nexo protocol security mug section 5 4 as an example message body to compute a mac example, we will use the acceptordiagnosticrequest message of the acquirer protocol as input of the acceptordiagnosticrequest mac, the xml encoded body diagnosticrequest of the message is \<dgnstcreq> \<envt> \<acqrrparamsvrsn>2010 01 01t08 00 00\</acqrrparamsvrsn> \<mrchntid> \<id>epasmer001\</id> \<tp>merc\</tp> \</mrchntid> \<poiid> \<id>66000001\</id> \<tp>opoi\</tp> \<issr>acqr\</issr> \</poiid> \</envt> \</dgnstcreq> once unnecessary spaces and carriage returns are removed, acceptordiagnosticrequest is xml \<dgnstcreq>\<envt>\<acqrrparamsvrsn>2010 01 01t08 00 00\</acqrrparamsvrsn>\<mrchntid>\<id>epasmer001\</id>\<tp>merc\</tp>\</mrchntid>\<poiid>\<id>66000001\</id>\<tp>opoi\</tp>\<issr>acqr\</issr>\</poiid>\</envt>\</dgnstcreq> cmac examples with aes256 these examples demonstrate mac calculation using cmac aes 256 the examples are taken from the nexo protocol security mug in these examples, the following aes256 bit key is used 34790642 381cc5ff ad606931 49b29d39 340e5482 748c37eb 8a6f4bf3 f3ef39e2 example cmac with aes256 see nexo protocol security mug section 5 4 6 for a full breakdown of the mac generation process example commands using openssl are shown below \# save the input message as message txt echo n "\<dgnstcreq>\<envt>\<acqrrparamsvrsn>2010 01 01t08 00 00\</acqrrparamsvrsn>\<mrchntid>\<id>epasmer001\</id>\<tp>merc\</tp>\</mrchntid>\<poiid>\<id>66000001\</id>\<tp>opoi\</tp>\<issr>acqr\</issr>\</poiid>\</envt>\</dgnstcreq>" > message txt \# save the key as hey hex echo n "34790642381cc5ffad60693149b29d39340e5482748c37eb8a6f4bf3f3ef39e2" > hey hex \# calculate the mac using cipher aws 256 openssl dgst mac cmac macopt cipher\ aes 256 cbc macopt hexkey $(cat key hex) message txt \# expected output \# cmac(message txt)= f712286e727f3f2681d74101e0be8de3 \# convert the hex to base64 echo "f712286e727f3f2681d74101e0be8de3" | xxd r p | base64 \# base64 representation of the mac 9xiobnj/pyab10eb4l6n4w== the mac of the message is f7 12 28 6e 72 7f 3f 26 81 d7 41 01 e0 be 8d e3 the base64 representation of the mac is 9xiobnj/pyab10eb4l6n4w== the resulting xml encoded structure is shown below \<sctytrlr> 	\<cntttp>auth\</cntttp> 	\<authntcddata> 	 \<rcpt> 	 \<keyidr> 	 \<keyid>specv1testkey\</keyid> 	 \<keyvrsn>1\</keyvrsn> 	 \</keyidr> 	 \</rcpt> 	 \<macalgo> 	 \<algo>cca3\</algo> 	 \</macalgo> 	 \<ncpsltdcntt> 	 \<cntttp>data\</cntttp> 	 \</ncpsltdcntt> 	 \<mac>9xiobnj/pyab10eb4l6n4w==\</mac> 	\</authntcddata> \</sctytrlr> example sha 512 cmac with aes256 see section nexo protocol security mug section 5 4 6 3 for a full breakdown of the mac generation process example commands using openssl are shown below \# generate a sha 512 digest of the message and save it to digest bin echo n "\<dgnstcreq>\<envt>\<acqrrparamsvrsn>2010 01 01t08 00 00\</acqrrparamsvrsn>\<mrchntid>\<id>epasmer001\</id>\<tp>merc\</tp>\</mrchntid>\<poiid>\<id>66000001\</id>\<tp>opoi\</tp>\<issr>acqr\</issr>\</poiid>\</envt>\</dgnstcreq>" | openssl dgst sha512 binary > digest bin \# check it matches the expected value xxd digest bin \# 00000000 09ae 273c ac0d ef0f 4243 e9dc 9dfc b076 '< bc v \# 00000010 5c7e b7ee a016 b5b2 8ae1 5406 5264 ce6a \\ t rd j \# 00000020 2573 045d ee09 d8d7 1696 840a 70c8 2140 %s ] p !@ \# 00000030 e4dc d52e 83f5 578e 9448 71b2 faef c117 w\ hq \# save the key as hey hex echo n "34790642381cc5ffad60693149b29d39340e5482748c37eb8a6f4bf3f3ef39e2" > hey hex \# calculate the mac using aes 256 cipher openssl dgst mac cmac macopt cipher\ aes 256 cbc macopt hexkey $(cat key hex) digest bin \# expected output \# cmac(digest bin)= 45a2bf16ea10fd137ea95eb8256ed92f \# convert the hex to base64 echo "45a2bf16ea10fd137ea95eb8256ed92f" | xxd r p | base64 \# base64 representation of the mac rak/fuoq/rn+qv64jw7zlw== the resulting xml encoded structure is shown below \<sctytrlr> 	\<cntttp>auth\</cntttp> 	\<authntcddata> 	 \<rcpt> 	 \<keyidr> 	 \<keyid>specv1testkey\</keyid> 	 \<keyvrsn>1\</keyvrsn> 	 \</keyidr> 	 \</rcpt> 	 \<macalgo> 	 \<algo>cma5\</algo> 	 \</macalgo> 	 \<ncpsltdcntt> 	 \<cntttp>data\</cntttp> 	 \</ncpsltdcntt> 	 \<mac>rak/fuoq/rn+qv64jw7zlw==\</mac> 	\</authntcddata> \</sctytrlr> example sha3 512 cmac with aes256 see section nexo protocol security mug section 5 4 6 4 for a full breakdown of the mac generation process example commands using openssl are shown below \# generate a sha 512 digest of the message and save it to digest bin echo n "\<dgnstcreq>\<envt>\<acqrrparamsvrsn>2010 01 01t08 00 00\</acqrrparamsvrsn>\<mrchntid>\<id>epasmer001\</id>\<tp>merc\</tp>\</mrchntid>\<poiid>\<id>66000001\</id>\<tp>opoi\</tp>\<issr>acqr\</issr>\</poiid>\</envt>\</dgnstcreq>" | openssl dgst sha3 512 binary > digest bin \# check it matches the expected value xxd digest bin \# 00000000 2b43 71a8 c795 0c12 9576 1200 eaec 3551 +cq v 5q \# 00000010 611b 1c50 8334 fc3c 5a39 78a5 57ec a37a a p 4 \<z9x w\ z \# 00000020 45fc f5a2 a6e3 afc3 d728 2888 cb0d 7c74 e (( |t \# 00000030 5e6f 7daf 0944 5f9e 5043 d820 2e69 a99c ^o} d pc i \# save the key as hey hex echo n "34790642381cc5ffad60693149b29d39340e5482748c37eb8a6f4bf3f3ef39e2" > hey hex \# calculate the mac using aes 256 cipher openssl dgst mac cmac macopt cipher\ aes 256 cbc macopt hexkey $(cat key hex) digest bin \# expected output \# cmac(digest bin)= ba35edff29f16bfe4ce7fc1bbf784aab \# convert the hex to base64 echo "ba35edff29f16bfe4ce7fc1bbf784aab" | xxd r p | base64 \# base64 representation of the mac ujxt/ynxa/5m5/wbv3hkqw== the resulting xml encoded structure is shown below \<sctytrlr> 	\<cntttp>auth\</cntttp> 	\<authntcddata> 	 \<rcpt> 	 \<keyidr> 	 \<keyid>specv1testkey\</keyid> 	 \<keyvrsn>1\</keyvrsn> 	 \</keyidr> 	 \</rcpt> 	 \<macalgo> 	 \<algo>cm33\</algo> 	 \</macalgo> 	 \<ncpsltdcntt> 	 \<cntttp>data\</cntttp> 	 \</ncpsltdcntt> 	 \<mac>ujxt/ynxa/5m5/wbv3hkqw==\</mac> 	\</authntcddata> \</sctytrlr> responses to common security trailer scenarios messages received by the gateway which do not include a security trailer will be rejected with a 400 bad request error messages which include an invalid (e g incorrectly formatted) security trailer will be rejected with a 400 bad request error messages whose security trailer cannot be verified against the sender's shared passphrase will be rejected with a 403 forbidden error
