001/* 002 * nimbus-jose-jwt 003 * 004 * Copyright 2012-2016, Connect2id Ltd and contributors. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.jose.crypto.impl; 019 020 021import java.io.ByteArrayOutputStream; 022import java.io.IOException; 023import java.security.MessageDigest; 024import java.security.NoSuchAlgorithmException; 025import javax.crypto.SecretKey; 026import javax.crypto.spec.SecretKeySpec; 027 028import com.nimbusds.jose.EncryptionMethod; 029import com.nimbusds.jose.JOSEException; 030import com.nimbusds.jose.util.IntegerUtils; 031import com.nimbusds.jose.util.StandardCharset; 032 033 034/** 035 * Legacy implementation of a Concatenation Key Derivation Function (KDF) for 036 * use by the deprecated {@code A128CBC+HS256} and {@code A256CBC+HS512} 037 * encryption methods. Provides static methods for deriving the Content 038 * Encryption Key (CEK) and the Content Integrity Key (CIK) from a Content 039 * Master Key (CMKs). 040 * 041 * <p>See draft-ietf-jose-json-web-encryption-08, appendices A.4 and A.5. 042 * 043 * <p>See NIST.800-56A. 044 * 045 * @author Vladimir Dzhuvinov 046 * @version 2018-01-04 047 */ 048public class LegacyConcatKDF { 049 050 051 /** 052 * The four byte array (32-byte) representation of 1. 053 */ 054 private static final byte[] ONE_BYTES = { (byte)0, (byte)0, (byte)0, (byte)1 }; 055 056 057 /** 058 * The four byte array (32-bit) representation of 0. 059 */ 060 private static final byte[] ZERO_BYTES = { (byte)0, (byte)0, (byte)0, (byte)0 }; 061 062 063 /** 064 * The byte array representation of the string "Encryption". 065 */ 066 private static final byte[] ENCRYPTION_BYTES = { 067 068 (byte)69, (byte)110, (byte)99, (byte)114, (byte)121, (byte)112, (byte)116, (byte)105, (byte)111, (byte)110 069 }; 070 071 072 /** 073 * The byte array representation of the string "Integrity". 074 */ 075 private static final byte[] INTEGRITY_BYTES = { 076 077 (byte)73, (byte)110, (byte)116, (byte)101, (byte)103, (byte)114, (byte)105, (byte)116, (byte)121 078 }; 079 080 081 /** 082 * Generates a Content Encryption Key (CEK) from the specified 083 * Content Master Key (CMK) and JOSE encryption method. 084 * 085 * @param key The Content Master Key (CMK). Must not be {@code null}. 086 * @param enc The JOSE encryption method. Must not be {@code null}. 087 * @param epu The value of the encryption PartyUInfo header parameter, 088 * {@code null} if not specified. 089 * @param epv The value of the encryption PartyVInfo header parameter, 090 * {@code null} if not specified. 091 * 092 * @return The generated AES CEK. 093 * 094 * @throws JOSEException If CEK generation failed. 095 */ 096 public static SecretKey generateCEK(final SecretKey key, 097 final EncryptionMethod enc, 098 final byte[] epu, 099 final byte[] epv) 100 throws JOSEException { 101 102 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 103 104 int hashBitLength; 105 106 try { 107 // Write [0, 0, 0, 1] 108 baos.write(ONE_BYTES); 109 110 // Append CMK 111 byte[] cmkBytes = key.getEncoded(); 112 baos.write(cmkBytes); 113 114 // Append [CEK-bit-length...] 115 final int cmkBitLength = cmkBytes.length * 8; 116 hashBitLength = cmkBitLength; 117 final int cekBitLength = cmkBitLength / 2; 118 byte[] cekBitLengthBytes = IntegerUtils.toBytes(cekBitLength); 119 baos.write(cekBitLengthBytes); 120 121 // Append the encryption method value, e.g. "A128CBC+HS256" 122 byte[] encBytes = enc.toString().getBytes(StandardCharset.UTF_8); 123 baos.write(encBytes); 124 125 // Append encryption PartyUInfo=Datalen || Data 126 if (epu != null) { 127 128 baos.write(IntegerUtils.toBytes(epu.length)); 129 baos.write(epu); 130 131 } else { 132 baos.write(ZERO_BYTES); 133 } 134 135 // Append encryption PartyVInfo=Datalen || Data 136 if (epv != null) { 137 138 baos.write(IntegerUtils.toBytes(epv.length)); 139 baos.write(epv); 140 141 } else { 142 baos.write(ZERO_BYTES); 143 } 144 145 // Append "Encryption" label 146 baos.write(ENCRYPTION_BYTES); 147 148 } catch (IOException e) { 149 150 throw new JOSEException(e.getMessage(), e); 151 } 152 153 // Write out 154 byte[] hashInput = baos.toByteArray(); 155 156 MessageDigest md; 157 158 try { 159 // SHA-256 or SHA-512 160 md = MessageDigest.getInstance("SHA-" + hashBitLength); 161 162 } catch (NoSuchAlgorithmException e) { 163 164 throw new JOSEException(e.getMessage(), e); 165 } 166 167 byte[] hashOutput = md.digest(hashInput); 168 169 byte[] cekBytes = new byte[hashOutput.length / 2]; 170 System.arraycopy(hashOutput, 0, cekBytes, 0, cekBytes.length); 171 172 return new SecretKeySpec(cekBytes, "AES"); 173 } 174 175 176 /** 177 * Generates a Content Integrity Key (CIK) from the specified 178 * Content Master Key (CMK) and JOSE encryption method. 179 * 180 * @param key The Content Master Key (CMK). Must not be {@code null}. 181 * @param enc The JOSE encryption method. Must not be {@code null}. 182 * @param epu The value of the encryption PartyUInfo header parameter, 183 * {@code null} if not specified. 184 * @param epv The value of the encryption PartyVInfo header parameter, 185 * {@code null} if not specified. 186 * 187 * @return The generated HMAC SHA CIK. 188 * 189 * @throws JOSEException If CIK generation failed. 190 */ 191 public static SecretKey generateCIK(final SecretKey key, 192 final EncryptionMethod enc, 193 final byte[] epu, 194 final byte[] epv) 195 throws JOSEException { 196 197 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 198 199 int hashBitLength; 200 int cikBitLength; 201 202 try { 203 // Write [0, 0, 0, 1] 204 baos.write(ONE_BYTES); 205 206 // Append CMK 207 byte[] cmkBytes = key.getEncoded(); 208 baos.write(cmkBytes); 209 210 // Append [CIK-bit-length...] 211 final int cmkBitLength = cmkBytes.length * 8; 212 hashBitLength = cmkBitLength; 213 cikBitLength = cmkBitLength; 214 byte[] cikBitLengthBytes = IntegerUtils.toBytes(cikBitLength); 215 baos.write(cikBitLengthBytes); 216 217 // Append the encryption method value, e.g. "A128CBC+HS256" 218 byte[] encBytes = enc.toString().getBytes(StandardCharset.UTF_8); 219 baos.write(encBytes); 220 221 // Append encryption PartyUInfo=Datalen || Data 222 if (epu != null) { 223 224 baos.write(IntegerUtils.toBytes(epu.length)); 225 baos.write(epu); 226 227 } else { 228 baos.write(ZERO_BYTES); 229 } 230 231 // Append encryption PartyVInfo=Datalen || Data 232 if (epv != null) { 233 234 baos.write(IntegerUtils.toBytes(epv.length)); 235 baos.write(epv); 236 237 } else { 238 baos.write(ZERO_BYTES); 239 } 240 241 // Append "Encryption" label 242 baos.write(INTEGRITY_BYTES); 243 244 } catch (IOException e) { 245 246 throw new JOSEException(e.getMessage(), e); 247 } 248 249 // Write out 250 byte[] hashInput = baos.toByteArray(); 251 252 MessageDigest md; 253 254 try { 255 // SHA-256 or SHA-512 256 md = MessageDigest.getInstance("SHA-" + hashBitLength); 257 258 } catch (NoSuchAlgorithmException e) { 259 260 throw new JOSEException(e.getMessage(), e); 261 } 262 263 // HMACSHA256 or HMACSHA512 264 return new SecretKeySpec(md.digest(hashInput), "HMACSHA" + cikBitLength); 265 } 266 267 268 /** 269 * Prevents public instantiation. 270 */ 271 private LegacyConcatKDF() { 272 273 } 274}