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