IBM CCA Key Wrapping Details - Part 1: Fixed-length Symmetric Key Tokens
The IBM Common Cryptographic Architecture (CCA) API deals with keys, primarily in the form of key tokens, which may be stored in a Key Data Set (e.g. CKDS) under a "label" or held in memory.
The key value which is stored in such a token may be "in the clear", or encrypted using the applicable master key or a key-encrypting key (KEK), or a derivative thereof (as you will see). Master keys are loaded into the IBM cryptocard(s), away from prying eyes. Using the cryptography parlance, a key value is "wrapped" before being placed in an encrypted key token. The wrapping process is more complex than simple encryption of the clear key value using the master key.
The purpose of wrapping the key value is to:
Except where the length of the key is being obfuscated, the wrapping process does not significantly increase the space required to store the key. Typically, the wrapped key is the same size as the clear key.
Motivation
These days, I'm spending a lot of time working with customers on various projects involving cryptography, typically where more than one Hardware Security Module (HSM) vendor is in play. In this scenario, I need a firm handle on how keys might be transported and shared between HSMs, which in the IBM CCA context, means understanding key tokens and key wrapping with some precision.
Having said that, the intent of this article is educational (for me and the reader). You should always use the IBM CCA API to manipulate keys and key tokens. I provide a lot of detail in this series of articles, and I have gone to some lengths to ensure the information is accurate at the time of writing. However, my understanding of the key wrapping process may not be complete. Manual wrapping and unwrapping of keys, for any purpose other than education, is strongly discouraged.
There is an adage in the cryptography world that the privacy of encrypted material depends on the secrecy of the key used to encrypt it, not the secrecy of the algorithm. In this context, although I will give you the recipe to wrap and unwrap keys, you will need to know the clear value of your master keys and/or KEKs, if you want to play along in your own environment. Do not expose production master keys or KEKs as clear values.
Finally, I have discovered and verified the key wrapping process under z/OS and I provide some sample code in REXX. I have no reason to suspect there are any material differences in the CCA key wrapping process on distributed platforms, but I have not verified this.
CCA Key Token Varieties
IBM CCA has been around for decades. Over the years, as algorithms (and attacks) have emerged, requirements have driven an evolution of key token formats to the point now where more than a dozen distinct key token formats and wrapping methods exist. However, in my experience, the vast bulk of key tokens in use are fixed-length DES tokens. Yes, the world is moving to AES for symmetric cryptography, but progress is slow. These fixed-length tokens are the smallest, oldest and simplest methods of key storage in the CCA world, so they make a good place to start. I will describe fixed-length AES tokens in this article, and move to variable-length symmetric and asymmetric key tokens and key wrapping in subsequent articles.
Anatomy of the DES Fixed-Length Key Token
IBM does document the layout of all key token varieties in detail. The DES fixed-length token format is described here: https://www.ibm.com/docs/en/zos/3.1.0?topic=tokens-des-fixed-length-key-token
Let me pick out a few elements of the key token that are pertinent to the key wrapping process.
The first byte of the token identifies the token as internal (x'01') or external (x'02'). An internal token is used to perform cryptographic operations. An external token is used to hold a key that is being imported or exported from the local CCA environment to another CCA or non-CCA environment. From the point of view of understanding how keys are wrapped, we can consider internal and external tokens as essentially the same thing - only the key used to encrypt the key value is different: the DES master key (DES-MK) for internal tokens and a KEK for an external token. Note that a KEK is stored in an internal key token before it can be used to import/export other keys.
As you might expect, the key token has three 8-byte fields to hold the components of a single-length, double-length or triple-length DES key. I will denote these encrypted key components by KA, KB and KC. I will denote the corresponding clear key components PA, PB and PC, respectively.
To enforce key separation, the key token holds two 8-byte control vector fields, which I will denote by CVL (i.e. left) and CVR (i.e. right). In the early days of CCA, only single-length and double-length DES keys were contemplated. CVL and CVR hold essentially the same information about how the key is to be used, but they differ in 3 key form bits (see: https://www.ibm.com/docs/en/zos/3.1.0?topic=table-key-form-bits-fff ). Note that CVR is set to nulls for a single-length key (except for WRAPENH3 wrapping - see below). Note also that an old-fashioned DATA key token (which is only used for raw encryption and decryption) has CVL=nulls and CVR=nulls. I'm not going to venture into all the considerations around control vectors. Suffice to say that they play a role in key wrapping, which I will describe in detail.
WRAP-ECB
The oldest and simplest method of key wrapping is called WRAP-ECB. This is the default wrapping for single and double-length keys. It is not used for triple-length keys, as a rule.
Henceforth, I won't distinguish between the DES-MK for internal tokens and a KEK for external tokens - I will simply refer to a KEK, which is used to wrap the key value stored in the token. The KEK can be a double or triple-length key.
WRAP-ECB wraps the left half of the clear key, PA, and the right half, PB, separately. The key which encrypts PA, denoted by KEKA, is formed by computing the bitwise exclusive OR (XOR) of the KEK with 2 or 3 copies of CVL, depending on the length of the KEK. The key which encrypts PB, denoted by KEKB, is formed the same way, using copies of CVR. When wrapping a single-length key, KB is nulls.
If I use eECB(K, D) to denote DES encryption, in ECB mode, of data, D, using key, K, and I use || to denote concatenation, then I can express the WRAP-ECB wrapping process as follows:
KEKA = KEK XOR (CVL || CVL [ || CVL ] )
KEKB = KEK XOR (CVR || CVR [ || CVR ] )
KA = eECB(KEKA, PA)
KB = nulls for single-length key
KB = eECB(KEKB, PB) for double-length key
Let's work through a couple of examples.
First, let's export an internal double-length OPINENC key, K, (used in payment card transactions to encrypt PIN blocks), with a known key value, using an EXPORTER key (again with known value) as our KEK:
PA = 7F6BBF198C0BA713 <--+-- clear key
PB = 029B23E9CD549840 <--+
CVL = 0024770003410000 <-- indicates OPINENC key
CVR = 0024770003210000 <-- note: nearly identical to CVL
KEK = 297AFE70267985CE49B362C15B0E29C7
XOR: 00247700034100000024770003410000 <-- CVL || CVL
KEKA = 295E8970253885CE499715C1584F29C7
KA = EC34568487D16E33 <-- eECB(KEKA, PA)
KEK = 297AFE70267985CE49B362C15B0E29C7
XOR: 00247700032100000024770003210000 <-- CVR || CVR
KEKB = 295E8970255885CE499715C1582F29C7
KB = 56FC2C8EDC1B9605 <-- eECB(KEKB, PB)
So:
PA || PB = 7F6BBF198C0BA713029B23E9CD549840 <-- clear key
KA || KB = EC34568487D16E3356FC2C8EDC1B9605 <-- wrapped key
Now let's try unwrapping the key in the internal token for our OPINENC key. This time, the KEK is the DES-MK loaded into the cryptocard(s). dECB(K, D) denotes DES decryption in ECB mode of data, D, using key, K.
KA = C410F58E150FE9CF <--+-- taken from internal key token
KB = EBC8CF8DC2D606E9 <--+
CVL = 0024770003410000 <--+-- same as external token
CVR = 0024770003210000 <--+
KEK = 435B867F2FBF43E06716B5852C29AE46 <-- DES-MK
XOR: 00247700034100000024770003410000 <-- CVL || CVL
KEKA = 437FF17F2CFE43E06732C2852F68AE46
PA = 7F6BBF198C0BA713 <-- dECB(KEKA, KA)
KEK = 435B867F2FBF43E06716B5852C29AE46
XOR: 00247700032100000024770003210000 <-- CVR || CVR
KEKB = 437FF17F2C9E43E06732C2852F08AE46
PB = 029B23E9CD549840 <-- dECB(KEKB, KB)
So:
KA || KB = C410F58E150FE9CFEBC8CF8DC2D606E9 <-- wrapped key (under DES-MK)
PA || PB = 7F6BBF198C0BA713029B23E9CD549840 <-- clear key (correct value)
WRAP-ENH
Over time, various potential vulnerabilities in the WRAP-ECB wrapping technique have come to light. Accordingly, IBM introduced an enhanced wrapping method denoted by WRAP-ENH, for single and double-length keys which:
For any crypto-nerds who want to understand the vulnerabilities of WRAP-ECB and how these enhanced countermeasures might strengthen key token security, I refer you to the PhD thesis of Michael Bond from 2004 (see: https://www.mike-bond.com/research/Thesis.pdf ).
Anyway, for the rest of us, I will present the steps of the enhanced wrapping process with a focus on what to do, rather than why we are doing it. We will wrap our OPINENC key (above), using the DES-MK, as we go.
Extending the KEK
The first step is to extend the KEK to be a triple-length key, if necessary, by repeating the first 8 bytes of a double-length KEK at the end of the key. Using our DES-MK from above,
KEK = 435B867F2FBF43E0 6716B5852C29AE46
becomes:
KEK = 435B867F2FBF43E0 6716B5852C29AE46 435B867F2FBF43E0
Deriving the Wrapping Key (WK)
The WK is derived from the KEK using the KDF in Counter Mode algorithm described in NIST Special Publication 800-38D (see: https://csrc.nist.gov/pubs/sp/800/38/d/final ) using HMAC with SHA-256 hash as the Pseudo-Random Function (PRF) used by the Key Derivation Function (KDF).
The HMAC message authentication algorithm (see: https://en.wikipedia.org/wiki/HMAC ), using our extended KEK as the key and SHA-256 (see: https://en.wikipedia.org/wiki/SHA-2 ) as the hash function, produces a 32-byte authentication code as output. We take the first 24 bytes of this output as our derived key.
The HMAC algorithm requires a message as input to compute the corresponding authentication code. For the WRAP-ENH wrapping method, this message is (using the notation of NIST 800-38D):
message = i || label || separator || context || L
= 00000001454E48414E434544575241503230313000000000C0
where:
i = 00000001 (i.e. counter = 1)
label = 454E48414E4345445752415032303130 <-- ENHANCEDWRAP2010 (ASCII)
separator = 00
context = (not used)
L = 000000C0 (i.e. WK length = 192 bits)
Note that the label corresponds to the string ENHANCEDWRAP2010 in ASCII.
I know what you are thinking... This is all very nice, but how do I compute a HMAC with SHA-256 hash? Well, you use the IBM CCA API, of course! Here's an example of a REXX script to compute a HMAC given a specified key and message. This script calls CSNBKTB2 (see: https://www.ibm.com/docs/en/zos/3.1.0?topic=keys-key-token-build2-csnbktb2-csnektb2 ) to build a clear HMAC token, then calls CSNBHMG (see: https://www.ibm.com/docs/en/zos/3.1.0?topic=messages-hmac-generate-csnbhmg-csnbhmg1-csnehmg-csnehmg1 ) to compute the HMAC authentication code:
/* REXX */
/* compute HMAC SHA-256 */
arg key message .
if key = '' then do
say 'Usage: hmac key_hex [data_hex]'
return 8
end
key = x2c(key)
message = x2c(message)
/* build a clear HMAC key token */
return_code = d2c(0, 4)
reason_code = d2c(0, 4)
exit_data_length = d2c(0, 4)
exit_data = ''
rule_array_count = d2c(5, 4)
rule_array = 'INTERNALHMAC KEY-CLR MAC GENERATE'
clear_key_bit_length = d2c(length(key) * 8, 4)
clear_key_value = key
key_name_length = d2c(0, 4)
key_name = ''
user_associated_data_length = d2c(0, 4)
user_associated_data = ''
token_data_length = d2c(0, 4)
token_data = ''
service_data_length = d2c(0, 4)
service_data = ''
target_key_token_length = d2c(725, 4)
target_key_token = copies('00'x, 725)
address linkpgm 'CSNBKTB2' ,
'return_code reason_code' ,
'exit_data_length exit_data' ,
'rule_array_count rule_array' ,
'clear_key_bit_length clear_key_value' ,
'key_name_length key_name' ,
'user_associated_data_length user_associated_data' ,
'token_data_length token_data' ,
'service_data_length service_data' ,
'target_key_token_length target_key_token'
rc = c2d(return_code)
rsn = c2d(reason_code)
if rc > 4 then do
say 'CSNBKTB2 failed. rc='rc', rsn='rsn
return 12
end
/* compute HMAC SHA-256 */
return_code = d2c(0, 4)
reason_code = d2c(0, 4)
exit_data_length = d2c(0, 4)
exit_data = ''
rule_array_count = d2c(2, 4)
rule_array = 'HMAC SHA-256 '
key_identifier_length = target_key_token_length
key_identifier = target_key_token
text_length = d2c(length(message), 4)
text = message
chaining_vector_length = d2c(128, 4)
chaining_vector = copies('00'x, 128)
mac_length = d2c(64, 4)
mac = copies('00'x, 64)
address linkpgm 'CSNBHMG' ,
'return_code reason_code' ,
'exit_data_length exit_data' ,
'rule_array_count rule_array' ,
'key_identifier_length key_identifier' ,
'text_length text' ,
'chaining_vector_length chaining_vector' ,
'mac_length mac'
rc = c2d(return_code)
rsn = c2d(reason_code)
if rc > 4 then do
say 'CSNBHMG failed. rc='rc', rsn='rsn
return 12
end
say ' key:' c2x(key)
say 'message:' c2x(message)
say ' mac:' c2x(left(mac, c2d(mac_length)))
At this point, let me pause for a message from our sponsor (me)...
As elegant as this REXX code is, it is a bit verbose. By way of comparison, this is the equivalent script written using my ZCCREXX productivity tool (see: https://github.com/admattingly/ZCCREXX ):
/* REXX */
/* compute HMAC SHA-256 */
arg key message .
if key = '' then do
say 'Usage: hmac key_hex [data_hex]'
return 8
end
key = x2c(key)
message = x2c(message)
call ZCCREXX(ON) /* install ZCCREXX host command environment */
address ZCCREXX /* send commands to ZCCREXX by default */
/* build a clear HMAC key token */
rule_array = zcpack('INTERNAL HMAC KEY-CLR MAC GENERATE')
rule_array_count = length(rule_array) / 8
clear_key_bit_length = length(key) * 8
clear_key_value = key
target_key_token_length = 725
'CSNBKTB2'
if return_code > 4 then do
call ZCCREXX(OFF) /* remove ZCCREXX host command environment */
return 12
end
/* compute HMAC SHA-256 */
rule_array = zcpack('HMAC SHA-256')
rule_array_count = length(rule_array) / 8
key_identifier_length = target_key_token_length
key_identifier = target_key_token
text_length = length(message)
text = message
chaining_vector_length = 128
mac_length = 64
'CSNBHMG'
call ZCCREXX(OFF) /* remove ZCCREXX host command environment */
if return_code > 4 then do
return 12
end
say ' key:' c2x(key)
say 'message:' c2x(message)
say ' mac:' c2x(left(mac, mac_length))
Either way, the output is the same:
key: 435B867F2FBF43E06716B5852C29AE46435B867F2FBF43E0
message: 00000001454E48414E434544575241503230313000000000C0
mac: EC03105E8A3663716E43DF28FE22BC86E2C68D0C7985092DDF9F03B1281453AE
So:
WK = EC03105E8A3663716E43DF28FE22BC86E2C68D0C7985092D
Apply Control Vector
Now we create a variant of the WK by performing a bitwise exclusive OR with three copies of the left control vector:
old WK = EC03105E8A3663716E43DF28FE22BC86E2C68D0C7985092D
XOR: 002477000341000000247700034100000024770003410000
new WK = EC27675E897763716E67A828FD63BC86E2E2FA0C7AC4092D
Wrap a Single-Length Key
If the key to be wrapped is a single-length DES key, we simply encrypt the clear key value using our WK:
KA = eECB(WK, PA)
KB = 0000000000000000
Wrap a Double-Length Key
If the key to be wrapped is a double-length key, something very interesting happens - we bind or chain the two halves of the key together. Without going into the why, this thwarts a variety of potential attacks.
领英推荐
Here's the recipe:
I have assumed to this point that the reader is familiar with symmetric encryption and its various modes. If you are a bit rusty with this stuff (see: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation ), the essential difference between ECB mode and CBC mode is that ECB mode encrypts each 8-byte block of plaintext independently (i.e. the ciphertext depends only on the key), whereas CBC mode uses the ciphertext from the previous block as input to the encryption of the next block. So by making recovery of PA dependent on knowledge of PB, and using CBC (left-to-right, dependent mode of encryption) we have locked the two halves of the key together - they must be wrapped and unwrapped as a single unit.
Using eCBC(K, D) to denote DES encryption in CBC mode of data, D, using key, K, we have:
PB = 029B23E9CD549840 <-- clear key (right half)
HB = 285894FF9F80F9AAB4DC2A201CAC6A62ACB36DA9 <-- SHA-1(PB)
IB = 285894FF9F80F9AA <-- left(HB, 8)
PA = 7F6BBF198C0BA713 <-- clear key (left half)
JA = 57332BE6138B5EB9 <-- PA XOR IB
PD = 57332BE6138B5EB9029B23E9CD549840 <-- JA || PB
WK = EC27675E897763716E67A828FD63BC86E2E2FA0C7AC4092D
KD = 3E23ED77F1D3519156E72B01EB89F224 <-- eCBC(WK, PD)
KA = 3E23ED77F1D35191 <--+-- wrapped key
KB = 56E72B01EB89F224 <--+
WRAPENH2
To wrap a triple-length key, WRAP-ECB and WRAP-ENH are not fit for purpose. WRAPENH2 is a fairly straightforward extension of WRAP-ENH, with a couple of minor adjustments.
The key derivation process is identical, except the application of the control vector variant will necessarily produce a slightly different derived key because the control vector has different key form bits (to indicate it is a triple-length key) and the ENH-ONLY bit is set in both control vectorts, since a triple-length key cannot be re-wrapped using WRAP-ECB.
Essentially, if we are using the correct control vector, we get the right derived key, using the same process as WRAP-ENH.
Key Part Chaining
The key component chaining process for WRAPENH2 is necessarily different to WRAP-ENH because we have three key components to chain, instead of two.
The other slight wrinkle is that by the time WRAPENH2 came along, concerns were emerging about the security of SHA-1 as a hashing scheme, so SHA-256 took its place in the chaining process.
Let's work through an example by adding a third component to our previous clear key:
PC = EC6737640E670489 <-- third clear key component
HC = B37E3141BF16BE2BC8596793A6E7A119C5668A1C33F230E0E52BE2F816F63DDE <-- SHA-256(PC)
IC = B37E3141BF16BE2B <-- left(HC, 8)
PB = 029B23E9CD549840 <-- second clear key component
JB = B1E512A87242266B <-- PB XOR IC
HB = 6302B61A732FA102336FDCE0F947821D21E1769D8DB281045EA4C9B0D4567ED2 <-- SHA-256(JB)
IB = 6302B61A732FA102 <-- left(HB, 8)
PA = 7F6BBF198C0BA713 <-- first clear key component
JA = 1C690903FF240611 <-- PA XOR IB
PD = 1C690903FF240611B1E512A87242266BEC6737640E670489 <-- JA || JB || PC
Now we form our derived key, WK, as before, noting the slightly different CVL for a triple-length OPINENC key:
CVL = 0024770003600081
old WK = EC03105E8A3663716E43DF28FE22BC86E2C68D0C7985092D
XOR: 002477000360008100247700036000810024770003600081
new WK = EC27675E895663F06E67A828FD42BC07E2E2FA0C7AE509AC
Finally, encrypt the chained payload, PD, using key, WK, in CBC mode:
PD = 1C690903FF240611B1E512A87242266BEC6737640E670489
WK = EC27675E895663F06E67A828FD42BC07E2E2FA0C7AE509AC
KD = D0C3AF3D59D0EF5ACA5DF0E63E4C1AB642E22A99FCCBA344 <-- eCBC(WK, PD)
KA = D0C3AF3D59D0EF5A
KB = CA5DF0E63E4C1AB6
KC = 42E22A99FCCBA344
WRAPENH3
At this point, everything in the CCA world was sailing along nicely. All tastes were catered for, in terms of key length/strength. Key component chaining added robustness to key wrapping. What could possibly go wrong?
Enter the payment card regulator, PCI, with new requirements for key wrapping (see: https://community.ibm.com/community/user/ibmz-and-linuxone/blogs/richard-kisley1/2021/05/21/ibm-tdes-key-token-wrapenh3-for-pci-pin?communityKey=6593e27b-caf6-4f6c-a8a8-10b62a02509c for a comprehensive discussion). In a nutshell, two requirements were imposed:
Derived Keys
For WRAPENH3, we derive two keys - one for wrapping our key and the other for computing a TDES-CMAC of the key token to generate an authentication code. KDF in Counter Mode is deployed using HMAC with SHA-256 as the PRF, as before. What changes is the label component of the message used in generating the derived keys.
For the wrapping key, a label of "WRAPENH3KEY-ENCR" (in ASCII) is used.
For the TDES-CMAC key, a label of "WRAPENH3KEY-CMAC" (in ASCII) is used.
Let's derive the wrapping key corresponding to our DES-MK as the KEK, by extending this key to triple-length, as before, then constructing a message with the appropriate label and computing the HMAC with SHA-256:
KEK = 435B867F2FBF43E0 6716B5852C29AE46
becomes:
KEK = 435B867F2FBF43E0 6716B5852C29AE46 435B867F2FBF43E0
message = i || label || separator || context || L
= 0000000157524150454E48334B45592D454E435200000000C0
where:
i = 00000001 (i.e. counter = 1)
label = 57524150454E48334B45592D454E4352 <-- WRAPENH3KEY-ENCR (ASCII)
separator = 00
context = (not used)
L = 000000C0 (i.e. WK length = 192 bits)
key: 435B867F2FBF43E06716B5852C29AE46435B867F2FBF43E0
message: 0000000157524150454E48334B45592D454E435200000000C0
mac: 47A9990E7AC99A3010D371E5A451DD0CEDAE3E69479CBD9D7D981E607EE1DD5B
So:
WK = 47A9990E7AC99A3010D371E5A451DD0CEDAE3E69479CBD9D
Now repeat the process to derive our TDES-CMAC key, CK:
KEK = 435B867F2FBF43E0 6716B5852C29AE46
becomes:
KEK = 435B867F2FBF43E0 6716B5852C29AE46 435B867F2FBF43E0
message = i || label || separator || context || L
= 0000000157524150454E48334B45592D434D414300000000C0
where:
i = 00000001 (i.e. counter = 1)
label = 57524150454E48334B45592D434D4143 <-- WRAPENH3KEY-CMAC (ASCII)
separator = 00
context = (not used)
L = 000000C0 (i.e. WK length = 192 bits)
key: 435B867F2FBF43E06716B5852C29AE46435B867F2FBF43E0
message: 0000000157524150454E48334B45592D434D414300000000C0
mac: 8FB32654B38746D5E58AC39D561EFB4FF21C71F2003FA2075997FEFE18CE9FFA
So:
CK = 8FB32654B38746D5E58AC39D561EFB4FF21C71F2003FA207
PCI has deprecated the use of variant schemes, like "XOR with control vector", so the WK and CK are used unaltered.
Key Extension
To obfuscate the key length, the clear key is extended with nulls to 24-bytes, then chained in the same way as for WRAPENH2.
For example, extending and chaining our original double-length key:
PC = 0000000000000000 <-- third clear key component (or extension)
HC = AF5570F5A1810B7AF78CAF4BC70A660F0DF51E42BAF91D4DE5B2328DE0E83DFC <-- SHA-256(PC)
IC = AF5570F5A1810B7A <-- left(HC, 8)
PB = 029B23E9CD549840 <-- second clear key component (or extension)
JB = ADCE531C6CD5933A <-- PB XOR IC
HB = EDEB0A878F8EC04DD12870A8AAF4E96F617B62C3969C55C900401EEBB57813B8 <-- SHA-256(JB)
IB = EDEB0A878F8EC04D <-- left(HB, 8)
PA = 7F6BBF198C0BA713 <-- first clear key component
JA = 9280B59E0385675E <-- PA XOR IB
PD = 9280B59E0385675EADCE531C6CD5933A0000000000000000 <-- JA || JB || PC
Now we use the WK to wrap the PD (chained 24-byte key) as before:
PD = 9280B59E0385675EADCE531C6CD5933A0000000000000000
WK = 47A9990E7AC99A3010D371E5A451DD0CEDAE3E69479CBD9D
KD = 83C2907AE32866B45B66EE0AF6B470E52A3C8203E3290807 <-- eCBC(WK, PD)
KA = 83C2907AE32866B4
KB = 5B66EE0AF6B470E5
KC = 2A3C8203E3290807
Authentication Code
To compute the authentication code, we construct a modified version of the 64-byte key token as the message, as follows:
To illustrate, let's take the encrypted internal key token for our double-length OPINENC key and modify it according to these specifications:
token( 0 - 15): 010000000000C060 E9C34D4D87BB9BDB
message( 0 - 15): 010000000000C060 E9C34D4D87BB9BDB
<------------ copy ------------->
token(16 - 31): 83C2907AE32866B4 5B66EE0AF6B470E5
message(16 - 31): 7F6BBF198C0BA713 029B23E9CD549840
<----- PA -----> <----- PB ----->
token(32 - 47): 0024770003600081 738D3E4A89FCACE3
message(32 - 47): 0024770003600081 0000000000000000
<----- CVL ----> <---- nulls --->
token(48 - 63): 2A3C8203E3290807 0000000039F9EC5D
message(48 - 63): 0000000000000000 0000000000000000
<----- PC -----> < copy >< nulls>
Note: The key token contains no indicators of the length of the key. The CVL's key form bits indicate a triple-length key, irrespective of the actual length of the key. This is to keep PCI happy.
We compute the TDES-CMAC of this message, using CK as the key, which yields:
CMAC = 738D3E4A89FCACE3
When an encrypted token is constructed by CCA, this authentication code is placed in the CVR field of the token (see bytes 40-47 of the above token).
At the time of writing, I have covered all the wrapping schemes for fixed-length DES key tokens. I don't expect any more to emerge, as the world seems to be moving away from proprietary key tokens and wrapping schemes, in favour of things like TR-31 (more on that in a subsequent article) and as already noted, DES is giving way to AES as the symmetric encryption algorithm of choice.
AES Fixed-Length Internal Key Tokens
Relax! There is only one wrapping scheme for AES fixed-length internal tokens. These tokens hold AES DATA keys only (see: https://www.ibm.com/docs/en/zos/3.1.0?topic=tokens-aes-internal-fixed-length-key-token ). They have no mechanism to enforce key separation. They can only be used to perform raw AES encryption and decryption. AES variable-length key tokens (discussed in a future article) are the real deal, with key separation, sophisticated wrapping and other features that comply with the abovementioned PCI requirements.
The key in an AES fixed-length token is wrapped by extending the key with nulls out to a 256-bit (32-byte) value, then encrypting the extended key using the AES-MK in CBC mode. That's all there is to it.
Here's a worked example, where aeCBC(K, D) denotes AES encryption of data, D, using key, K, in CBC mode:
AES-MK = F2D3D33B8E59ECF82D61C036F6F085F8
3C715B99BE0D329EBF9AA2167B49CEBF
key = 7F6BBF198C0BA713029B23E9CD549840EC6737640E670489
PD = 7F6BBF198C0BA713029B23E9CD549840
EC6737640E6704890000000000000000
KD = 0E51F1CD9AC7D5D0A8BAD27DDA39E7B4 <--+-- aeCBC(AES-MK, PD)
D203EAC34EFBB161364C0F27B2F282B1 <--+
That was easy, wasn't it?
One More Thing...
There is one more fixed-length key token I haven't mentioned, the External RKX DES key token. I might come back to this one in a future article, once I've discussed asymmetric key formats and wrapping - it will make more sense then.
Acknowledgements
I must express my appreciation to Eric Rossman and Richard Kisley for their kind assistance.
Improving digital healthcare for all Australians, while sharing great music across the airwaves ????
4 个月Interesting. Nice to see you still carrying the “z” flag Andrew.