001/* 002 * nimbus-jose-jwt 003 * 004 * Copyright 2012-2025, 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 com.nimbusds.jose.JOSEException; 022import com.nimbusds.jose.crypto.opts.CipherMode; 023import net.jcip.annotations.ThreadSafe; 024 025import javax.crypto.Cipher; 026import javax.crypto.SecretKey; 027import javax.crypto.spec.OAEPParameterSpec; 028import javax.crypto.spec.PSource; 029import javax.crypto.spec.SecretKeySpec; 030import java.security.AlgorithmParameters; 031import java.security.InvalidKeyException; 032import java.security.PrivateKey; 033import java.security.Provider; 034import java.security.interfaces.RSAPublicKey; 035import java.security.spec.AlgorithmParameterSpec; 036import java.security.spec.MGF1ParameterSpec; 037 038 039/** 040 * RSAES OAEP with SHA-256, SHA-384 and SHA-512 methods for Content Encryption 041 * Key (CEK) encryption and decryption. This class is thread-safe. 042 * 043 * @author Vladimir Dzhuvinov 044 * @author Justin Richer 045 * @author Peter Laurina 046 * @author Pankaj Yadav 047 * @version 2025-07-19 048 */ 049@ThreadSafe 050public class RSA_OAEP_SHA2 { 051 052 053 /** 054 * The JCA algorithm name for RSA-OAEP-256. 055 */ 056 private static final String RSA_OEAP_256_JCA_ALG = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"; 057 058 059 /** 060 * The JCA algorithm name for RSA-OAEP-384. 061 */ 062 private static final String RSA_OEAP_384_JCA_ALG = "RSA/ECB/OAEPWithSHA-384AndMGF1Padding"; 063 064 065 /** 066 * The JCA algorithm name for RSA-OAEP-512. 067 */ 068 private static final String RSA_OEAP_512_JCA_ALG = "RSA/ECB/OAEPWithSHA-512AndMGF1Padding"; 069 070 071 /** 072 * The JCA algorithm name for SHA-256. 073 */ 074 private static final String SHA_256_JCA_ALG = "SHA-256"; 075 076 077 /** 078 * The JCA algorithm name for SHA-384. 079 */ 080 private static final String SHA_384_JCA_ALG = "SHA-384"; 081 082 /** 083 * The JCA algorithm name for SHA-512. 084 */ 085 private static final String SHA_512_JCA_ALG = "SHA-512"; 086 087 088 /** 089 * Encrypts the specified Content Encryption Key (CEK). 090 * 091 * @param pub The public RSA key. Must not be {@code null}. 092 * @param cek The Content Encryption Key (CEK) to encrypt. Must 093 * not be {@code null}. 094 * @param shaBitSize The SHA-2 bit size. Must be 256, 384 or 512. 095 * @param mode The cipher mode to use. Must not be {@code null}. 096 * @param provider The JCA provider, {@code null} to use the 097 * default. 098 * 099 * @return The encrypted Content Encryption Key (CEK). 100 * 101 * @throws JOSEException If encryption failed. 102 */ 103 public static byte[] encryptCEK(final RSAPublicKey pub, 104 final SecretKey cek, 105 final int shaBitSize, 106 final CipherMode mode, 107 final Provider provider) 108 throws JOSEException { 109 110 assert mode == CipherMode.WRAP_UNWRAP || mode == CipherMode.ENCRYPT_DECRYPT; 111 112 final String jcaAlgName; 113 final String jcaShaAlgName; 114 final MGF1ParameterSpec mgf1ParameterSpec; 115 if (256 == shaBitSize) { 116 jcaAlgName = RSA_OEAP_256_JCA_ALG; 117 jcaShaAlgName = SHA_256_JCA_ALG; 118 mgf1ParameterSpec = MGF1ParameterSpec.SHA256; 119 } else if (384 == shaBitSize) { 120 jcaAlgName = RSA_OEAP_384_JCA_ALG; 121 jcaShaAlgName = SHA_384_JCA_ALG; 122 mgf1ParameterSpec = MGF1ParameterSpec.SHA384; 123 } else if (512 == shaBitSize) { 124 jcaAlgName = RSA_OEAP_512_JCA_ALG; 125 jcaShaAlgName = SHA_512_JCA_ALG; 126 mgf1ParameterSpec = MGF1ParameterSpec.SHA512; 127 } else { 128 throw new JOSEException("Unsupported SHA-2 bit size: " + shaBitSize); 129 } 130 131 try { 132 AlgorithmParameters algp = AlgorithmParametersHelper.getInstance("OAEP", provider); 133 AlgorithmParameterSpec paramSpec = new OAEPParameterSpec(jcaShaAlgName, "MGF1", mgf1ParameterSpec, PSource.PSpecified.DEFAULT); 134 algp.init(paramSpec); 135 Cipher cipher = CipherHelper.getInstance(jcaAlgName, provider); 136 cipher.init(mode.getForJWEEncrypter(), pub, algp); 137 if (mode == CipherMode.WRAP_UNWRAP) { 138 return cipher.wrap(cek); 139 } else { 140 // CipherMode.ENCRYPT_DECRYPT 141 return cipher.doFinal(cek.getEncoded()); 142 } 143 144 } catch (InvalidKeyException e) { 145 throw new JOSEException("Encryption failed due to invalid RSA key for SHA-" + shaBitSize + ": " 146 + "The RSA key may be too short, use a longer key", e); 147 } catch (Exception e) { 148 // java.security.NoSuchAlgorithmException 149 // java.security.NoSuchPaddingException 150 // javax.crypto.IllegalBlockSizeException 151 // javax.crypto.BadPaddingException 152 throw new JOSEException(e.getMessage(), e); 153 } 154 } 155 156 157 /** 158 * Decrypts the specified encrypted Content Encryption Key (CEK). 159 * 160 * @param priv The private RSA key. Must not be {@code null}. 161 * @param encryptedCEK The encrypted Content Encryption Key (CEK) to 162 * decrypt. Must not be {@code null}. 163 * @param shaBitSize The SHA-2 bit size. Must be 256 or 512. 164 * @param mode The cipher mode to use. Must not be 165 * {@code null}. 166 * @param provider The JCA provider, {@code null} to use the 167 * default. 168 * 169 * @return The decrypted Content Encryption Key (CEK). 170 * 171 * @throws JOSEException If decryption failed. 172 */ 173 public static SecretKey decryptCEK(final PrivateKey priv, 174 final byte[] encryptedCEK, 175 final int shaBitSize, 176 final CipherMode mode, 177 final Provider provider) 178 throws JOSEException { 179 180 assert mode == CipherMode.WRAP_UNWRAP || mode == CipherMode.ENCRYPT_DECRYPT; 181 182 final String jcaAlgName; 183 final String jcaShaAlgName; 184 final MGF1ParameterSpec mgf1ParameterSpec; 185 if (256 == shaBitSize) { 186 jcaAlgName = RSA_OEAP_256_JCA_ALG; 187 jcaShaAlgName = SHA_256_JCA_ALG; 188 mgf1ParameterSpec = MGF1ParameterSpec.SHA256; 189 } else if (384 == shaBitSize) { 190 jcaAlgName = RSA_OEAP_384_JCA_ALG; 191 jcaShaAlgName = SHA_384_JCA_ALG; 192 mgf1ParameterSpec = MGF1ParameterSpec.SHA384; 193 } else if (512 == shaBitSize) { 194 jcaAlgName = RSA_OEAP_512_JCA_ALG; 195 jcaShaAlgName = SHA_512_JCA_ALG; 196 mgf1ParameterSpec = MGF1ParameterSpec.SHA512; 197 } else { 198 throw new JOSEException("Unsupported SHA-2 bit size: " + shaBitSize); 199 } 200 201 try { 202 AlgorithmParameters algp = AlgorithmParametersHelper.getInstance("OAEP", provider); 203 AlgorithmParameterSpec paramSpec = new OAEPParameterSpec(jcaShaAlgName, "MGF1", mgf1ParameterSpec, PSource.PSpecified.DEFAULT); 204 algp.init(paramSpec); 205 Cipher cipher = CipherHelper.getInstance(jcaAlgName, provider); 206 cipher.init(mode.getForJWEDecrypter(), priv, algp); 207 208 if (mode == CipherMode.WRAP_UNWRAP) { 209 return (SecretKey) cipher.unwrap(encryptedCEK, "AES", Cipher.SECRET_KEY); 210 } else { 211 // CipherMode.ENCRYPT_DECRYPT 212 return new SecretKeySpec(cipher.doFinal(encryptedCEK), "AES"); 213 } 214 215 } catch (Exception e) { 216 // java.security.NoSuchAlgorithmException 217 // java.security.NoSuchPaddingException 218 // java.security.InvalidKeyException 219 // javax.crypto.IllegalBlockSizeException 220 // javax.crypto.BadPaddingException 221 throw new JOSEException(e.getMessage(), e); 222 } 223 } 224 225 226 /** 227 * Prevents public instantiation. 228 */ 229 private RSA_OAEP_SHA2() { } 230}