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 encryption key from the specified client secret. 027 * 028 * @param clientSecret The client secret. Must not be {@code null}. 029 * @param alg The JWE algorithm. Must not be {@code null}. 030 * @param enc The JWE method. Must not be {@code null}. 031 * 032 * @return The matching secret key (with algorithm set to "AES"). 033 * 034 * @throws JOSEException If the JWE algorithm or method is not 035 * supported. 036 */ 037 public static SecretKey deriveSecretKey(final Secret clientSecret, 038 final JWEAlgorithm alg, 039 final EncryptionMethod enc) 040 throws JOSEException { 041 042 if (JWEAlgorithm.DIR.equals(alg)) { 043 044 int cekBitLength = enc.cekBitLength(); 045 046 if (cekBitLength == 0) { 047 throw new JOSEException("Unsupported JWE method: enc=" + enc); 048 } 049 050 return deriveSecretKey(clientSecret, enc.cekBitLength()); 051 052 } else if (JWEAlgorithm.Family.AES_KW.contains(alg)) { 053 054 if (JWEAlgorithm.A128KW.equals(alg)) { 055 return deriveSecretKey(clientSecret, 128); 056 } else if (JWEAlgorithm.A192KW.equals(alg)) { 057 return deriveSecretKey(clientSecret, 192); 058 } else if (JWEAlgorithm.A256KW.equals(alg)) { 059 return deriveSecretKey(clientSecret, 256); 060 } 061 062 } else if (JWEAlgorithm.Family.AES_GCM_KW.contains(alg)) { 063 064 if (JWEAlgorithm.A128GCMKW.equals(alg)) { 065 return deriveSecretKey(clientSecret, 128); 066 } else if (JWEAlgorithm.A192GCMKW.equals(alg)) { 067 return deriveSecretKey(clientSecret, 192); 068 } else if (JWEAlgorithm.A256GCMKW.equals(alg)) { 069 return deriveSecretKey(clientSecret, 256); 070 } 071 } 072 073 throw new JOSEException("Unsupported JWE algorithm / method: alg=" + alg + " enc=" + enc); 074 } 075 076 077 /** 078 * Derives a secret encryption key from the specified client secret. 079 * 080 * @param clientSecret The client secret. Must not be {@code null}. 081 * @param bits The secret key bits (128, 192, 256, 384 or 512). 082 * 083 * @return The matching secret key (with algorithm set to "AES"). 084 * 085 * @throws JOSEException If the secret key bit size it not supported. 086 */ 087 public static SecretKey deriveSecretKey(final Secret clientSecret, final int bits) 088 throws JOSEException { 089 090 final int hashBitLength; 091 092 switch (bits) { 093 case 128: 094 case 192: 095 case 256: 096 hashBitLength = 256; 097 break; 098 case 384: 099 hashBitLength = 384; 100 break; 101 case 512: 102 hashBitLength = 512; 103 break; 104 default: 105 throw new JOSEException("Unsupported secret key length: " + bits + " bits"); 106 } 107 108 final byte[] hash; 109 110 try { 111 hash = MessageDigest.getInstance("SHA-" + hashBitLength).digest(clientSecret.getValueBytes()); 112 113 } catch (NoSuchAlgorithmException e) { 114 throw new JOSEException(e.getMessage(), e); 115 } 116 117 final byte[] keyBytes; 118 119 // Left-truncate if necessary 120 switch (bits) { 121 case 128: 122 keyBytes = ByteUtils.subArray(hash, ByteUtils.byteLength(256 - 128), ByteUtils.byteLength(128)); 123 break; 124 case 192: 125 keyBytes = ByteUtils.subArray(hash, ByteUtils.byteLength(256 - 192), ByteUtils.byteLength(192)); 126 break; 127 case 256: 128 keyBytes = hash; 129 break; 130 case 384: 131 keyBytes = hash; 132 break; 133 case 512: 134 keyBytes = hash; 135 break; 136 default: 137 throw new JOSEException("Unsupported secret key length: " + bits + " bits"); 138 } 139 140 return new SecretKeySpec(keyBytes, "AES"); 141 } 142 143 144 /** 145 * Prevents public instantiation. 146 */ 147 private SecretKeyDerivation() { 148 } 149}