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}