001/* 002 * oauth2-oidc-sdk 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.oauth2.sdk.jose; 019 020 021import java.security.MessageDigest; 022import java.security.NoSuchAlgorithmException; 023import javax.crypto.SecretKey; 024import javax.crypto.spec.SecretKeySpec; 025 026import com.nimbusds.jose.EncryptionMethod; 027import com.nimbusds.jose.JOSEException; 028import com.nimbusds.jose.JWEAlgorithm; 029import com.nimbusds.jose.util.ByteUtils; 030import com.nimbusds.oauth2.sdk.auth.Secret; 031 032 033/** 034 * Derives an AES secret key from a client secret. Intended for performing 035 * symmetric encryption and decryption with a client secret, as specified in 036 * <a href="http://openid.net/specs/openid-connect-core-1_0.html#Encryption">OpenID 037 * Connect Core 1.0, section 10.2</a>. 038 */ 039public class SecretKeyDerivation { 040 041 042 /** 043 * Derives a secret encryption key from the specified client secret. 044 * 045 * @param clientSecret The client secret. Must not be {@code null}. 046 * @param alg The JWE algorithm. Must not be {@code null}. 047 * @param enc The JWE method. Must not be {@code null}. 048 * 049 * @return The matching secret key (with algorithm set to "AES"). 050 * 051 * @throws JOSEException If the JWE algorithm or method is not 052 * supported. 053 */ 054 public static SecretKey deriveSecretKey(final Secret clientSecret, 055 final JWEAlgorithm alg, 056 final EncryptionMethod enc) 057 throws JOSEException { 058 059 if (JWEAlgorithm.DIR.equals(alg)) { 060 061 int cekBitLength = enc.cekBitLength(); 062 063 if (cekBitLength == 0) { 064 throw new JOSEException("Unsupported JWE method: enc=" + enc); 065 } 066 067 return deriveSecretKey(clientSecret, enc.cekBitLength()); 068 069 } else if (JWEAlgorithm.Family.AES_KW.contains(alg)) { 070 071 if (JWEAlgorithm.A128KW.equals(alg)) { 072 return deriveSecretKey(clientSecret, 128); 073 } else if (JWEAlgorithm.A192KW.equals(alg)) { 074 return deriveSecretKey(clientSecret, 192); 075 } else if (JWEAlgorithm.A256KW.equals(alg)) { 076 return deriveSecretKey(clientSecret, 256); 077 } 078 079 } else if (JWEAlgorithm.Family.AES_GCM_KW.contains(alg)) { 080 081 if (JWEAlgorithm.A128GCMKW.equals(alg)) { 082 return deriveSecretKey(clientSecret, 128); 083 } else if (JWEAlgorithm.A192GCMKW.equals(alg)) { 084 return deriveSecretKey(clientSecret, 192); 085 } else if (JWEAlgorithm.A256GCMKW.equals(alg)) { 086 return deriveSecretKey(clientSecret, 256); 087 } 088 } 089 090 throw new JOSEException("Unsupported JWE algorithm / method: alg=" + alg + " enc=" + enc); 091 } 092 093 094 /** 095 * Derives a secret encryption key from the specified client secret. 096 * 097 * @param clientSecret The client secret. Must not be {@code null}. 098 * @param bits The secret key bits (128, 192, 256, 384 or 512). 099 * 100 * @return The matching secret key (with algorithm set to "AES"). 101 * 102 * @throws JOSEException If the secret key bit size it not supported. 103 */ 104 public static SecretKey deriveSecretKey(final Secret clientSecret, final int bits) 105 throws JOSEException { 106 107 final int hashBitLength; 108 109 switch (bits) { 110 case 128: 111 case 192: 112 case 256: 113 hashBitLength = 256; 114 break; 115 case 384: 116 hashBitLength = 384; 117 break; 118 case 512: 119 hashBitLength = 512; 120 break; 121 default: 122 throw new JOSEException("Unsupported secret key length: " + bits + " bits"); 123 } 124 125 final byte[] hash; 126 127 try { 128 hash = MessageDigest.getInstance("SHA-" + hashBitLength).digest(clientSecret.getValueBytes()); 129 } catch (NoSuchAlgorithmException e) { 130 throw new JOSEException(e.getMessage(), e); 131 } 132 133 final byte[] keyBytes; 134 135 // If necessary remove right-most bits to fit AES key length 136 // https://bitbucket.org/openid/connect/commits/15668505dbe66b290c7e84ecc2e7bff70d942012 137 switch (bits) { 138 case 128: 139 keyBytes = ByteUtils.subArray(hash, 0, ByteUtils.byteLength(128)); 140 break; 141 case 192: 142 keyBytes = ByteUtils.subArray(hash, 0, ByteUtils.byteLength(192)); 143 break; 144 case 256: 145 case 384: 146 case 512: 147 keyBytes = hash; 148 break; 149 default: 150 throw new JOSEException("Unsupported secret key length: " + bits + " bits"); 151 } 152 153 return new SecretKeySpec(keyBytes, "AES"); 154 } 155 156 157 /** 158 * Prevents public instantiation. 159 */ 160 private SecretKeyDerivation() { 161 } 162}