VYPR
Medium severity5.9NVD Advisory· Published May 14, 2024· Updated Apr 15, 2026

CVE-2024-30171

CVE-2024-30171

Description

An issue was discovered in Bouncy Castle Java TLS API and JSSE Provider before 1.78. Timing-based leakage may occur in RSA based handshakes because of exception processing.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.bouncycastle:bctls-fipsMaven
< 1.0.191.0.19
org.bouncycastle:bcprov-jdk18onMaven
< 1.781.78
org.bouncycastle:bcprov-jdk15onMaven
< 1.781.78
org.bouncycastle:bcprov-jdk15to18Maven
< 1.781.78
org.bouncycastle:bcprov-jdk14Maven
< 1.781.78
org.bouncycastle:bctls-jdk18onMaven
< 1.781.78
org.bouncycastle:bctls-jdk14Maven
< 1.781.78
org.bouncycastle:bctls-jdk15to18Maven
< 1.781.78
BouncyCastleNuGet
>= 0
BouncyCastle.CryptographyNuGet
< 2.3.12.3.1

Patches

3
c984b8bfd854

Improve TLS RSA PreMasterSecret decryption

https://github.com/bcgit/bc-csharpPeter DettmanMar 26, 2024via ghsa
2 files changed · +23 20
  • crypto/src/crypto/tls/TlsRsaKeyExchange.cs+20 18 modified
    @@ -11,11 +11,13 @@ namespace Org.BouncyCastle.Crypto.Tls
     {
         public static class TlsRsaKeyExchange
         {
    -        public static byte[] DecryptPreMasterSecret(byte[] encryptedPreMasterSecret, RsaKeyParameters privateKey,
    +        public const int PreMasterSecretLength = 48;
    +
    +        public static byte[] DecryptPreMasterSecret(byte[] buf, int off, int len, RsaKeyParameters privateKey,
                 int protocolVersion, SecureRandom secureRandom)
             {
    -            if (Arrays.IsNullOrEmpty(encryptedPreMasterSecret))
    -                throw new ArgumentException("cannot be null or empty", nameof(encryptedPreMasterSecret));
    +            if (buf == null || len < 1 || len > GetInputLimit(privateKey) || off < 0 || off > buf.Length - len)
    +                throw new ArgumentException("input not a valid EncryptedPreMasterSecret");
     
                 if (!privateKey.IsPrivate)
                     throw new ArgumentException("must be an RSA private key", nameof(privateKey));
    @@ -31,24 +33,24 @@ public static byte[] DecryptPreMasterSecret(byte[] encryptedPreMasterSecret, Rsa
                 secureRandom = CryptoServicesRegistrar.GetSecureRandom(secureRandom);
     
                 /*
    -             * Generate 48 random bytes we can use as a Pre-Master-Secret if the decrypted value is invalid.
    +             * Generate random bytes we can use as a Pre-Master-Secret if the decrypted value is invalid.
                  */
    -            byte[] result = new byte[48];
    +            byte[] result = new byte[PreMasterSecretLength];
                 secureRandom.NextBytes(result);
     
                 try
                 {
    -                BigInteger input = ConvertInput(modulus, encryptedPreMasterSecret);
    +                BigInteger input = ConvertInput(modulus, buf, off, len);
                     byte[] encoding = RsaBlinded(privateKey, input, secureRandom);
     
                     int pkcs1Length = (bitLength - 1) / 8;
    -                int plainTextOffset = encoding.Length - 48;
    +                int plainTextOffset = encoding.Length - PreMasterSecretLength;
     
    -                int badEncodingMask = CheckPkcs1Encoding2(encoding, pkcs1Length, 48);
    +                int badEncodingMask = CheckPkcs1Encoding2(encoding, pkcs1Length, PreMasterSecretLength);
                     int badVersionMask = -(Pack.BE_To_UInt16(encoding, plainTextOffset) ^ protocolVersion) >> 31;
                     int fallbackMask = badEncodingMask | badVersionMask;
     
    -                for (int i = 0; i < 48; ++i)
    +                for (int i = 0; i < PreMasterSecretLength; ++i)
                     {
                         result[i] = (byte)((result[i] & fallbackMask) | (encoding[plainTextOffset + i] & ~fallbackMask));
                     }
    @@ -69,6 +71,11 @@ public static byte[] DecryptPreMasterSecret(byte[] encryptedPreMasterSecret, Rsa
                 return result;
             }
     
    +        public static int GetInputLimit(RsaKeyParameters privateKey)
    +        {
    +            return (privateKey.Modulus.BitLength + 7) / 8;
    +        }
    +
             private static int CAddTo(int len, int cond, byte[] x, byte[] z)
             {
                 Debug.Assert(cond == 0 || cond == -1);
    @@ -116,16 +123,11 @@ private static int CheckPkcs1Encoding2(byte[] buf, int pkcs1Length, int plaintex
                 return errorSign >> 31;
             }
     
    -        private static BigInteger ConvertInput(BigInteger modulus, byte[] input)
    +        private static BigInteger ConvertInput(BigInteger modulus, byte[] buf, int off, int len)
             {
    -            int inputLimit = (modulus.BitLength + 7) / 8;
    -
    -            if (input.Length <= inputLimit)
    -            {
    -                BigInteger result = new BigInteger(1, input);
    -                if (result.CompareTo(modulus) < 0)
    -                    return result;
    -            }
    +            BigInteger result = BigIntegers.FromUnsignedByteArray(buf, off, len);
    +            if (result.CompareTo(modulus) < 0)
    +                return result;
     
                 throw new DataLengthException("input too large for RSA cipher.");
             }
    
  • crypto/src/tls/crypto/impl/bc/BcDefaultTlsCredentialedDecryptor.cs+3 2 modified
    @@ -45,21 +45,22 @@ public BcDefaultTlsCredentialedDecryptor(BcTlsCrypto crypto, Certificate certifi
     
             public virtual TlsSecret Decrypt(TlsCryptoParameters cryptoParams, byte[] ciphertext)
             {
    -            // TODO Keep only the decryption itself here - move error handling outside 
                 return SafeDecryptPreMasterSecret(cryptoParams, (RsaKeyParameters)m_privateKey, ciphertext);
             }
     
             /*
              * TODO[tls-ops] Probably need to make RSA encryption/decryption into TlsCrypto functions so that users can
              * implement "generic" encryption credentials externally
              */
    +        // TODO[api] Just inline this into Decrypt
             protected virtual TlsSecret SafeDecryptPreMasterSecret(TlsCryptoParameters cryptoParams,
                 RsaKeyParameters rsaServerPrivateKey, byte[] encryptedPreMasterSecret)
             {
                 ProtocolVersion expectedVersion = cryptoParams.RsaPreMasterSecretVersion;
     
                 byte[] preMasterSecret = Org.BouncyCastle.Crypto.Tls.TlsRsaKeyExchange.DecryptPreMasterSecret(
    -                encryptedPreMasterSecret, rsaServerPrivateKey, expectedVersion.FullVersion, m_crypto.SecureRandom);
    +                encryptedPreMasterSecret, 0, encryptedPreMasterSecret.Length, rsaServerPrivateKey,
    +                expectedVersion.FullVersion, m_crypto.SecureRandom);
     
                 return m_crypto.CreateSecret(preMasterSecret);
             }
    
d7d5e735abd6

added new RSA mode for better TLS unwrap operation relates to github #1528

https://github.com/bcgit/bc-javaDavid HookMar 23, 2024via ghsa
3 files changed · +107 35
  • prov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/CipherSpi.java+41 3 modified
    @@ -31,9 +31,13 @@
     import org.bouncycastle.crypto.encodings.OAEPEncoding;
     import org.bouncycastle.crypto.engines.RSABlindedEngine;
     import org.bouncycastle.crypto.params.ParametersWithRandom;
    +import org.bouncycastle.crypto.params.RSAKeyParameters;
    +import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
    +import org.bouncycastle.crypto.tls.TlsRsaKeyExchange;
     import org.bouncycastle.jcajce.provider.asymmetric.util.BaseCipherSpi;
     import org.bouncycastle.jcajce.provider.util.BadBlockException;
     import org.bouncycastle.jcajce.provider.util.DigestFactory;
    +import org.bouncycastle.jcajce.spec.TLSRSAPremasterSecretParameterSpec;
     import org.bouncycastle.jcajce.util.BCJcaJceHelper;
     import org.bouncycastle.jcajce.util.JcaJceHelper;
     import org.bouncycastle.util.Strings;
    @@ -49,6 +53,8 @@ public class CipherSpi
         private boolean                 publicKeyOnly = false;
         private boolean                 privateKeyOnly = false;
         private ErasableOutputStream    bOut = new ErasableOutputStream();
    +    private TLSRSAPremasterSecretParameterSpec tlsRsaSpec = null;
    +    private CipherParameters param = null;
     
         public CipherSpi(
             AsymmetricBlockCipher engine)
    @@ -262,9 +268,12 @@ protected void engineInit(
             SecureRandom random)
         throws InvalidKeyException, InvalidAlgorithmParameterException
         {
    -        CipherParameters param;
     
    -        if (params == null || params instanceof OAEPParameterSpec)
    +        this.tlsRsaSpec = null;
    +
    +        if (params == null
    +            || params instanceof OAEPParameterSpec
    +            || params instanceof TLSRSAPremasterSecretParameterSpec)
             {
                 if (key instanceof RSAPublicKey)
                 {
    @@ -291,7 +300,7 @@ else if (key instanceof RSAPrivateKey)
                     throw new InvalidKeyException("unknown key type passed to RSA");
                 }
                 
    -            if (params != null)
    +            if (params instanceof OAEPParameterSpec)
                 {
                     OAEPParameterSpec spec = (OAEPParameterSpec)params;
                     
    @@ -324,6 +333,14 @@ else if (key instanceof RSAPrivateKey)
     
                     cipher = new OAEPEncoding(new RSABlindedEngine(), digest, mgfDigest, ((PSource.PSpecified)spec.getPSource()).getValue());
                 }
    +            else if (params instanceof TLSRSAPremasterSecretParameterSpec)
    +            {
    +                if (!(param instanceof RSAPrivateCrtKeyParameters))
    +                {
    +                    throw new InvalidKeyException("RSA private key required for TLS decryption");
    +                }
    +                this.tlsRsaSpec = (TLSRSAPremasterSecretParameterSpec)params;
    +            }
             }
             else
             {
    @@ -403,6 +420,11 @@ protected byte[] engineUpdate(
             int     inputOffset,
             int     inputLen) 
         {
    +        if (tlsRsaSpec != null)
    +        {
    +            throw new IllegalStateException("RSA cipher initialized for TLS only");
    +        }
    +
             bOut.write(input, inputOffset, inputLen);
     
             if (cipher instanceof RSABlindedEngine)
    @@ -430,6 +452,11 @@ protected int engineUpdate(
             byte[]  output,
             int     outputOffset) 
         {
    +        if (tlsRsaSpec != null)
    +        {
    +            throw new IllegalStateException("RSA cipher initialized for TLS only");
    +        }
    +
             bOut.write(input, inputOffset, inputLen);
     
             if (cipher instanceof RSABlindedEngine)
    @@ -456,6 +483,12 @@ protected byte[] engineDoFinal(
             int     inputLen) 
             throws IllegalBlockSizeException, BadPaddingException
         {
    +        if (tlsRsaSpec != null)
    +        {
    +            ParametersWithRandom pWithR = (ParametersWithRandom)param;
    +            return TlsRsaKeyExchange.decryptPreMasterSecret(input, (RSAKeyParameters)pWithR.getParameters(), tlsRsaSpec.getProtocolVersion(), pWithR.getRandom());
    +        }
    +
             if (input != null)
             {
                 bOut.write(input, inputOffset, inputLen);
    @@ -487,6 +520,11 @@ protected int engineDoFinal(
             int     outputOffset) 
             throws IllegalBlockSizeException, BadPaddingException, ShortBufferException
         {
    +        if (tlsRsaSpec != null)
    +        {
    +            throw new IllegalStateException("RSA cipher initialized for TLS only");
    +        }
    +        
             if (outputOffset + engineGetOutputSize(inputLen) > output.length)
             {
                 throw new ShortBufferException("output buffer too short for input.");
    
  • prov/src/main/java/org/bouncycastle/jcajce/spec/TLSRSAPremasterSecretParameterSpec.java+19 0 added
    @@ -0,0 +1,19 @@
    +package org.bouncycastle.jcajce.spec;
    +
    +import java.security.spec.AlgorithmParameterSpec;
    +
    +public class TLSRSAPremasterSecretParameterSpec
    +    implements AlgorithmParameterSpec
    +{
    +    private final int protocolVersion;
    +
    +    public TLSRSAPremasterSecretParameterSpec(int protocolVersion)
    +    {
    +         this.protocolVersion = protocolVersion;
    +    }
    +
    +    public int getProtocolVersion()
    +    {
    +        return protocolVersion;
    +    }
    +}
    
  • tls/src/main/java/org/bouncycastle/tls/crypto/impl/jcajce/JceDefaultTlsCredentialedDecryptor.java+47 32 modified
    @@ -7,6 +7,7 @@
     
     import javax.crypto.Cipher;
     
    +import org.bouncycastle.jcajce.spec.TLSRSAPremasterSecretParameterSpec;
     import org.bouncycastle.tls.Certificate;
     import org.bouncycastle.tls.ProtocolVersion;
     import org.bouncycastle.tls.TlsCredentialedDecryptor;
    @@ -81,49 +82,63 @@ protected TlsSecret safeDecryptPreMasterSecret(TlsCryptoParameters cryptoParams,
              * RFC 5246 7.4.7.1.
              */
             ProtocolVersion expectedVersion = cryptoParams.getRSAPreMasterSecretVersion();
    +        byte[] M;
     
    -        /*
    -         * Generate 48 random bytes we can use as a Pre-Master-Secret, if the PKCS1 padding check should fail.
    -         */
    -        byte[] fallback = new byte[48];
    -        secureRandom.nextBytes(fallback);
    -
    -        byte[] M = Arrays.clone(fallback);
             try
             {
                 Cipher c = crypto.createRSAEncryptionCipher();
    -            c.init(Cipher.DECRYPT_MODE, rsaServerPrivateKey, secureRandom);
    -            byte[] m = c.doFinal(encryptedPreMasterSecret);
    -            if (m != null && m.length == 48)
    -            {
    -                M = m;
    -            }
    +
    +            c.init(Cipher.DECRYPT_MODE, rsaServerPrivateKey, new TLSRSAPremasterSecretParameterSpec(expectedVersion.getFullVersion()), secureRandom);
    +            M = c.doFinal(encryptedPreMasterSecret);
             }
    -        catch (Exception e)
    +        catch (Exception ex)
             {
    +            // Fallback
    +
                 /*
    -             * A TLS server MUST NOT generate an alert if processing an RSA-encrypted premaster secret message
    -             * fails, or the version number is not as expected. Instead, it MUST continue the handshake with a
    -             * randomly generated premaster secret.
    +             * Generate 48 random bytes we can use as a Pre-Master-Secret, if the PKCS1 padding check should fail.
                  */
    -        }
    +            byte[] fallback = new byte[48];
    +            secureRandom.nextBytes(fallback);
     
    -        /*
    -         * Compare the version number in the decrypted Pre-Master-Secret with the legacy_version field from
    -         * the ClientHello. If they don't match, continue the handshake with the randomly generated 'fallback'
    -         * value.
    -         *
    -         * NOTE: The comparison and replacement must be constant-time.
    -         */
    -        int mask = (expectedVersion.getMajorVersion() ^ (M[0] & 0xFF))
    -                 | (expectedVersion.getMinorVersion() ^ (M[1] & 0xFF));
    +            M = Arrays.clone(fallback);
    +            try
    +            {
    +                Cipher c = crypto.createRSAEncryptionCipher();
    +
    +                c.init(Cipher.DECRYPT_MODE, rsaServerPrivateKey, secureRandom);
    +                byte[] m = c.doFinal(encryptedPreMasterSecret);
    +                if (m != null && m.length == 48)
    +                {
    +                    M = m;
    +                }
    +            }
    +            catch (Exception e)
    +            {
    +                /*
    +                 * A TLS server MUST NOT generate an alert if processing an RSA-encrypted premaster secret message
    +                 * fails, or the version number is not as expected. Instead, it MUST continue the handshake with a
    +                 * randomly generated premaster secret.
    +                 */
    +            }
    +
    +            /*
    +             * Compare the version number in the decrypted Pre-Master-Secret with the legacy_version field from
    +             * the ClientHello. If they don't match, continue the handshake with the randomly generated 'fallback'
    +             * value.
    +             *
    +             * NOTE: The comparison and replacement must be constant-time.
    +             */
    +            int mask = (expectedVersion.getMajorVersion() ^ (M[0] & 0xFF))
    +                | (expectedVersion.getMinorVersion() ^ (M[1] & 0xFF));
     
    -        // 'mask' will be all 1s if the versions matched, or else all 0s.
    -        mask = (mask - 1) >> 31;
    +            // 'mask' will be all 1s if the versions matched, or else all 0s.
    +            mask = (mask - 1) >> 31;
     
    -        for (int i = 0; i < 48; i++)
    -        {
    -            M[i] = (byte)((M[i] & mask) | (fallback[i] & ~mask));
    +            for (int i = 0; i < 48; i++)
    +            {
    +                M[i] = (byte)((M[i] & mask) | (fallback[i] & ~mask));
    +            }
             }
     
             return crypto.createSecret(M);
    
e0569dcb1dea

Improvements to PKCS1Encoding

https://github.com/bcgit/bc-javaPeter DettmanNov 10, 2023via ghsa
3 files changed · +402 133
  • core/src/main/java/org/bouncycastle/crypto/encodings/PKCS1Encoding.java+115 119 modified
    @@ -233,52 +233,86 @@ private byte[] encodeBlock(
         }
     
         /**
    -     * Checks if the argument is a correctly PKCS#1.5 encoded Plaintext
    -     * for encryption.
    -     *
    -     * @param encoded The Plaintext.
    -     * @param pLen    Expected length of the plaintext.
    -     * @return Either 0, if the encoding is correct, or -1, if it is incorrect.
    +     * Check the argument is a valid encoding with type 1. Returns the plaintext length if valid, or -1 if invalid.
          */
    -    private static int checkPkcs1Encoding(byte[] encoded, int pLen)
    +    private static int checkPkcs1Encoding1(byte[] buf)
         {
    -        int correct = 0;
    -        /*
    -         * Check if the first two bytes are 0 2
    -         */
    -        correct |= (encoded[0] ^ 2);
    +        int foundZeroMask = 0;
    +        int lastPadPos = 0;
     
    -        /*
    -         * Now the padding check, check for no 0 byte in the padding
    -         */
    -        int plen = encoded.length - (
    -            pLen /* Length of the PMS */
    -                + 1 /* Final 0-byte before PMS */
    -        );
    +        // The first byte should be 0x01
    +        int badPadSign = -((buf[0] & 0xFF) ^ 0x01);
     
    -        for (int i = 1; i < plen; i++)
    +        // There must be a zero terminator for the padding somewhere
    +        for (int i = 1; i < buf.length; ++i)
             {
    -            int tmp = encoded[i];
    -            tmp |= tmp >> 1;
    -            tmp |= tmp >> 2;
    -            tmp |= tmp >> 4;
    -            correct |= (tmp & 1) - 1;
    +            int padByte = buf[i] & 0xFF;
    +            int is0x00Mask = ((padByte ^ 0x00) - 1) >> 31;
    +            int is0xFFMask = ((padByte ^ 0xFF) - 1) >> 31;
    +            lastPadPos ^= i & ~foundZeroMask & is0x00Mask;
    +            foundZeroMask |= is0x00Mask;
    +            badPadSign |= ~(foundZeroMask | is0xFFMask);
             }
     
    -        /*
    -         * Make sure the padding ends with a 0 byte.
    -         */
    -        correct |= encoded[encoded.length - (pLen + 1)];
    +        // The header should be at least 10 bytes
    +        badPadSign |= lastPadPos - 9;
     
    -        /*
    -         * Return 0 or 1, depending on the result.
    -         */
    -        correct |= correct >> 1;
    -        correct |= correct >> 2;
    -        correct |= correct >> 4;
    -        return ~((correct & 1) - 1);
    +        int plaintextLength = buf.length - 1 - lastPadPos;
    +        return plaintextLength | badPadSign >> 31;
         }
     
    +    /**
    +     * Check the argument is a valid encoding with type 2. Returns the plaintext length if valid, or -1 if invalid.
    +     */
    +    private static int checkPkcs1Encoding2(byte[] buf)
    +    {
    +        int foundZeroMask = 0;
    +        int lastPadPos = 0;
    +
    +        // The first byte should be 0x02
    +        int badPadSign = -((buf[0] & 0xFF) ^ 0x02);
    +
    +        // There must be a zero terminator for the padding somewhere
    +        for (int i = 1; i < buf.length; ++i)
    +        {
    +            int padByte = buf[i] & 0xFF;
    +            int is0x00Mask = ((padByte ^ 0x00) - 1) >> 31;
    +            lastPadPos ^= i & ~foundZeroMask & is0x00Mask;
    +            foundZeroMask |= is0x00Mask;
    +        }
    +
    +        // The header should be at least 10 bytes
    +        badPadSign |= lastPadPos - 9;
    +
    +        int plaintextLength = buf.length - 1 - lastPadPos;
    +        return plaintextLength | badPadSign >> 31;
    +    }
    +
    +    /**
    +     * Check the argument is a valid encoding with type 2 of a plaintext with the given length. Returns 0 if
    +     * valid, or -1 if invalid.
    +     */
    +    private static int checkPkcs1Encoding2(byte[] buf, int plaintextLength)
    +    {
    +        // The first byte should be 0x02
    +        int badPadSign = -((buf[0] & 0xFF) ^ 0x02);
    +
    +        int lastPadPos = buf.length - 1 - plaintextLength;
    +
    +        // The header should be at least 10 bytes
    +        badPadSign |= lastPadPos - 9;
    +
    +        // All pad bytes before the last one should be non-zero
    +        for (int i = 1; i < lastPadPos; ++i)
    +        {
    +            badPadSign |= (buf[i] & 0xFF) - 1;
    +        }
    +
    +        // Last pad byte should be zero
    +        badPadSign |= -(buf[lastPadPos] & 0xFF);
    +
    +        return badPadSign >> 31;
    +    }
     
         /**
          * Decode PKCS#1.5 encoding, and return a random value if the padding is not correct.
    @@ -298,132 +332,94 @@ private byte[] decodeBlockOrRandom(byte[] in, int inOff, int inLen)
                 throw new InvalidCipherTextException("sorry, this method is only for decryption, not for signing");
             }
     
    -        byte[] block = engine.processBlock(in, inOff, inLen);
    -        byte[] random;
    -        if (this.fallback == null)
    +        int plaintextLength = this.pLen;
    +
    +        byte[] random = fallback;
    +        if (fallback == null)
             {
    -            random = new byte[this.pLen];
    +            random = new byte[plaintextLength];
                 this.random.nextBytes(random);
             }
    -        else
    +
    +        int badPadMask = 0;
    +        int strictBlockSize = engine.getOutputBlockSize();
    +        byte[] block = engine.processBlock(in, inOff, inLen);
    +
    +        byte[] data = block;
    +        if (block.length != strictBlockSize)
             {
    -            random = fallback;
    +            if (useStrictLength || block.length < strictBlockSize)
    +            {
    +                data = blockBuffer;
    +            }
             }
     
    -        byte[] data = (useStrictLength & (block.length != engine.getOutputBlockSize())) ? blockBuffer : block;
    +        badPadMask |= checkPkcs1Encoding2(data, plaintextLength);
     
    -        /*
    -         * Check the padding.
    -         */
    -        int correct = PKCS1Encoding.checkPkcs1Encoding(data, this.pLen);
    -        
             /*
              * Now, to a constant time constant memory copy of the decrypted value
              * or the random value, depending on the validity of the padding.
              */
    -        byte[] result = new byte[this.pLen];
    -        for (int i = 0; i < this.pLen; i++)
    +        int dataOff = data.length - plaintextLength; 
    +        byte[] result = new byte[plaintextLength];
    +        for (int i = 0; i < plaintextLength; ++i)
             {
    -            result[i] = (byte)((data[i + (data.length - pLen)] & (~correct)) | (random[i] & correct));
    +            result[i] = (byte)((data[dataOff + i] & ~badPadMask) | (random[i] & badPadMask));
             }
     
    -        Arrays.fill(data, (byte)0);
    +        Arrays.fill(block, (byte)0);
    +        Arrays.fill(blockBuffer, 0, Math.max(0, blockBuffer.length - block.length), (byte)0);
     
             return result;
         }
     
         /**
          * @throws InvalidCipherTextException if the decrypted block is not in PKCS1 format.
          */
    -    private byte[] decodeBlock(
    -        byte[] in,
    -        int inOff,
    -        int inLen)
    +    private byte[] decodeBlock(byte[] in, int inOff, int inLen)
             throws InvalidCipherTextException
         {
             /*
              * If the length of the expected plaintext is known, we use a constant-time decryption.
              * If the decryption fails, we return a random value.
              */
    -        if (this.pLen != -1)
    +        if (forPrivateKey && this.pLen != -1)
             {
                 return this.decodeBlockOrRandom(in, inOff, inLen);
             }
     
    +        int strictBlockSize = engine.getOutputBlockSize();
             byte[] block = engine.processBlock(in, inOff, inLen);
    -        boolean incorrectLength = (useStrictLength & (block.length != engine.getOutputBlockSize()));
    -
    -        byte[] data;
    -        if (block.length < getOutputBlockSize())
    -        {
    -            data = blockBuffer;
    -        }
    -        else
    -        {
    -            data = block;
    -        }
    -
    -        byte type = data[0];
    -
    -        boolean badType;
    -        if (forPrivateKey)
    -        {
    -            badType = (type != 2);
    -        }
    -        else
    -        {
    -            badType = (type != 1);
    -        }
    -
    -        //
    -        // find and extract the message block.
    -        //
    -        int start = findStart(type, data);
    -
    -        start++;           // data should start at the next byte
     
    -        if (badType | start < HEADER_LENGTH)
    -        {
    -            Arrays.fill(data, (byte)0);
    -            throw new InvalidCipherTextException("block incorrect");
    -        }
    +        boolean incorrectLength = useStrictLength & (block.length != strictBlockSize);
     
    -        // if we get this far, it's likely to be a genuine encoding error
    -        if (incorrectLength)
    +        byte[] data = block;
    +        if (block.length < strictBlockSize)
             {
    -            Arrays.fill(data, (byte)0);
    -            throw new InvalidCipherTextException("block incorrect size");
    +            data = blockBuffer;
             }
     
    -        byte[] result = new byte[data.length - start];
    -
    -        System.arraycopy(data, start, result, 0, result.length);
    -
    -        return result;
    -    }
    +        int plaintextLength = forPrivateKey ? checkPkcs1Encoding2(data) : checkPkcs1Encoding1(data);
     
    -    private int findStart(byte type, byte[] block)
    -        throws InvalidCipherTextException
    -    {
    -        int start = -1;
    -        boolean padErr = false;
    -
    -        for (int i = 1; i != block.length; i++)
    +        try
             {
    -            byte pad = block[i];
    -
    -            if (pad == 0 & start < 0)
    +            if (plaintextLength < 0)
                 {
    -                start = i;
    +                throw new InvalidCipherTextException("block incorrect");
    +            }
    +            if (incorrectLength)
    +            {
    +                throw new InvalidCipherTextException("block incorrect size");
                 }
    -            padErr |= (type == 1 & start < 0 & pad != (byte)0xff);
    -        }
     
    -        if (padErr)
    +            byte[] result = new byte[plaintextLength];
    +            System.arraycopy(data, data.length - plaintextLength, result, 0, plaintextLength);
    +            return result;
    +        }
    +        finally
             {
    -            return -1;
    +            Arrays.fill(block, (byte)0);
    +            Arrays.fill(blockBuffer, 0, Math.max(0, blockBuffer.length - block.length), (byte)0);
             }
    -
    -        return start;
         }
     }
    
  • prov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/CipherSpi.java+24 14 modified
    @@ -29,7 +29,6 @@
     import org.bouncycastle.crypto.InvalidCipherTextException;
     import org.bouncycastle.crypto.encodings.ISO9796d1Encoding;
     import org.bouncycastle.crypto.encodings.OAEPEncoding;
    -import org.bouncycastle.crypto.encodings.PKCS1Encoding;
     import org.bouncycastle.crypto.engines.RSABlindedEngine;
     import org.bouncycastle.crypto.params.ParametersWithRandom;
     import org.bouncycastle.jcajce.provider.asymmetric.util.BaseCipherSpi;
    @@ -200,7 +199,7 @@ protected void engineSetPadding(
             }
             else if (pad.equals("PKCS1PADDING"))
             {
    -            cipher = new PKCS1Encoding(new RSABlindedEngine());
    +            cipher = new CustomPKCS1Encoding(new RSABlindedEngine());
             }
             else if (pad.equals("ISO9796-1PADDING"))
             {
    @@ -531,15 +530,26 @@ private byte[] getOutput()
         {
             try
             {
    -            return cipher.processBlock(bOut.getBuf(), 0, bOut.size());
    -        }
    -        catch (InvalidCipherTextException e)
    -        {
    -            throw new BadBlockException("unable to decrypt block", e);
    -        }
    -        catch (ArrayIndexOutOfBoundsException e)
    -        {
    -            throw new BadBlockException("unable to decrypt block", e);
    +            byte[] output;
    +            try
    +            {
    +                output = cipher.processBlock(bOut.getBuf(), 0, bOut.size());
    +            }
    +            catch (InvalidCipherTextException e)
    +            {
    +                throw new BadBlockException("unable to decrypt block", e);
    +            }
    +            catch (ArrayIndexOutOfBoundsException e)
    +            {
    +                throw new BadBlockException("unable to decrypt block", e);
    +            }
    +
    +            if (output == null)
    +            {
    +                throw new BadBlockException("unable to decrypt block", null);
    +            }
    +
    +            return output;
             }
             finally
             {
    @@ -565,7 +575,7 @@ static public class PKCS1v1_5Padding
         {
             public PKCS1v1_5Padding()
             {
    -            super(new PKCS1Encoding(new RSABlindedEngine()));
    +            super(new CustomPKCS1Encoding(new RSABlindedEngine()));
             }
         }
     
    @@ -574,7 +584,7 @@ static public class PKCS1v1_5Padding_PrivateOnly
         {
             public PKCS1v1_5Padding_PrivateOnly()
             {
    -            super(false, true, new PKCS1Encoding(new RSABlindedEngine()));
    +            super(false, true, new CustomPKCS1Encoding(new RSABlindedEngine()));
             }
         }
     
    @@ -583,7 +593,7 @@ static public class PKCS1v1_5Padding_PublicOnly
         {
             public PKCS1v1_5Padding_PublicOnly()
             {
    -            super(true, false, new PKCS1Encoding(new RSABlindedEngine()));
    +            super(true, false, new CustomPKCS1Encoding(new RSABlindedEngine()));
             }
         }
     
    
  • prov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/CustomPkcs1Encoding.java+263 0 added
    @@ -0,0 +1,263 @@
    +package org.bouncycastle.jcajce.provider.asymmetric.rsa;
    +
    +import java.security.SecureRandom;
    +
    +import org.bouncycastle.crypto.AsymmetricBlockCipher;
    +import org.bouncycastle.crypto.CipherParameters;
    +import org.bouncycastle.crypto.CryptoServicesRegistrar;
    +import org.bouncycastle.crypto.InvalidCipherTextException;
    +import org.bouncycastle.crypto.encodings.PKCS1Encoding;
    +import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
    +import org.bouncycastle.crypto.params.ParametersWithRandom;
    +import org.bouncycastle.util.Arrays;
    +import org.bouncycastle.util.Properties;
    +
    +/**
    + * this does your basic PKCS 1 v1.5 padding - whether or not you should be using this
    + * depends on your application - see PKCS1 Version 2 for details.
    + */
    +class CustomPKCS1Encoding
    +    implements AsymmetricBlockCipher
    +{
    +    private static final int HEADER_LENGTH = 10;
    +
    +    private SecureRandom random;
    +    private AsymmetricBlockCipher engine;
    +    private boolean forEncryption;
    +    private boolean forPrivateKey;
    +    private boolean useStrictLength;
    +    private byte[] blockBuffer;
    +
    +    /**
    +     * Basic constructor.
    +     *
    +     * @param cipher
    +     */
    +    CustomPKCS1Encoding(AsymmetricBlockCipher cipher)
    +    {
    +        this.engine = cipher;
    +        this.useStrictLength = useStrict();
    +    }
    +
    +    //
    +    // for J2ME compatibility
    +    //
    +    private boolean useStrict()
    +    {
    +        if (Properties.isOverrideSetTo(PKCS1Encoding.NOT_STRICT_LENGTH_ENABLED_PROPERTY, true))
    +        {
    +            return false;
    +        }
    +
    +        return !Properties.isOverrideSetTo(PKCS1Encoding.STRICT_LENGTH_ENABLED_PROPERTY, false);
    +    }
    +
    +    public AsymmetricBlockCipher getUnderlyingCipher()
    +    {
    +        return engine;
    +    }
    +
    +    public void init(boolean forEncryption, CipherParameters param)
    +    {
    +        AsymmetricKeyParameter kParam;
    +
    +        if (param instanceof ParametersWithRandom)
    +        {
    +            ParametersWithRandom rParam = (ParametersWithRandom)param;
    +
    +            this.random = rParam.getRandom();
    +            kParam = (AsymmetricKeyParameter)rParam.getParameters();
    +        }
    +        else
    +        {
    +            kParam = (AsymmetricKeyParameter)param;
    +            if (!kParam.isPrivate() && forEncryption)
    +            {
    +                this.random = CryptoServicesRegistrar.getSecureRandom();
    +            }
    +        }
    +
    +        engine.init(forEncryption, param);
    +
    +        this.forPrivateKey = kParam.isPrivate();
    +        this.forEncryption = forEncryption;
    +        this.blockBuffer = new byte[engine.getOutputBlockSize()];
    +    }
    +
    +    public int getInputBlockSize()
    +    {
    +        int baseBlockSize = engine.getInputBlockSize();
    +
    +        if (forEncryption)
    +        {
    +            return baseBlockSize - HEADER_LENGTH;
    +        }
    +        else
    +        {
    +            return baseBlockSize;
    +        }
    +    }
    +
    +    public int getOutputBlockSize()
    +    {
    +        int baseBlockSize = engine.getOutputBlockSize();
    +
    +        if (forEncryption)
    +        {
    +            return baseBlockSize;
    +        }
    +        else
    +        {
    +            return baseBlockSize - HEADER_LENGTH;
    +        }
    +    }
    +
    +    public byte[] processBlock(byte[] in, int inOff, int inLen) throws InvalidCipherTextException
    +    {
    +        if (forEncryption)
    +        {
    +            return encodeBlock(in, inOff, inLen);
    +        }
    +        else
    +        {
    +            return decodeBlock(in, inOff, inLen);
    +        }
    +    }
    +
    +    private byte[] encodeBlock(byte[] in, int inOff, int inLen) throws InvalidCipherTextException
    +    {
    +        if (inLen > getInputBlockSize())
    +        {
    +            throw new IllegalArgumentException("input data too large");
    +        }
    +
    +        byte[] block = new byte[engine.getInputBlockSize()];
    +
    +        if (forPrivateKey)
    +        {
    +            block[0] = 0x01;                        // type code 1
    +
    +            for (int i = 1; i != block.length - inLen - 1; i++)
    +            {
    +                block[i] = (byte)0xFF;
    +            }
    +        }
    +        else
    +        {
    +            random.nextBytes(block);                // random fill
    +
    +            block[0] = 0x02;                        // type code 2
    +
    +            //
    +            // a zero byte marks the end of the padding, so all
    +            // the pad bytes must be non-zero.
    +            //
    +            for (int i = 1; i != block.length - inLen - 1; i++)
    +            {
    +                while (block[i] == 0)
    +                {
    +                    block[i] = (byte)random.nextInt();
    +                }
    +            }
    +        }
    +
    +        block[block.length - inLen - 1] = 0x00;       // mark the end of the padding
    +        System.arraycopy(in, inOff, block, block.length - inLen, inLen);
    +
    +        return engine.processBlock(block, 0, block.length);
    +    }
    +
    +    /**
    +     * Check the argument is a valid encoding with type 1. Returns the plaintext length if valid, or -1 if invalid.
    +     */
    +    private static int checkPkcs1Encoding1(byte[] buf)
    +    {
    +        int foundZeroMask = 0;
    +        int lastPadPos = 0;
    +
    +        // The first byte should be 0x01
    +        int badPadSign = -((buf[0] & 0xFF) ^ 0x01);
    +
    +        // There must be a zero terminator for the padding somewhere
    +        for (int i = 1; i < buf.length; ++i)
    +        {
    +            int padByte = buf[i] & 0xFF;
    +            int is0x00Mask = ((padByte ^ 0x00) - 1) >> 31;
    +            int is0xFFMask = ((padByte ^ 0xFF) - 1) >> 31;
    +            lastPadPos ^= i & ~foundZeroMask & is0x00Mask;
    +            foundZeroMask |= is0x00Mask;
    +            badPadSign |= ~(foundZeroMask | is0xFFMask);
    +        }
    +
    +        // The header should be at least 10 bytes
    +        badPadSign |= lastPadPos - 9;
    +
    +        int plaintextLength = buf.length - 1 - lastPadPos;
    +        return plaintextLength | badPadSign >> 31;
    +    }
    +
    +    /**
    +     * Check the argument is a valid encoding with type 2. Returns the plaintext length if valid, or -1 if invalid.
    +     */
    +    private static int checkPkcs1Encoding2(byte[] buf)
    +    {
    +        int foundZeroMask = 0;
    +        int lastPadPos = 0;
    +
    +        // The first byte should be 0x02
    +        int badPadSign = -((buf[0] & 0xFF) ^ 0x02);
    +
    +        // There must be a zero terminator for the padding somewhere
    +        for (int i = 1; i < buf.length; ++i)
    +        {
    +            int padByte = buf[i] & 0xFF;
    +            int is0x00Mask = ((padByte ^ 0x00) - 1) >> 31;
    +            lastPadPos ^= i & ~foundZeroMask & is0x00Mask;
    +            foundZeroMask |= is0x00Mask;
    +        }
    +
    +        // The header should be at least 10 bytes
    +        badPadSign |= lastPadPos - 9;
    +
    +        int plaintextLength = buf.length - 1 - lastPadPos;
    +        return plaintextLength | badPadSign >> 31;
    +    }
    +
    +    /**
    +     * @throws InvalidCipherTextException if the decrypted block is not in PKCS1 format.
    +     */
    +    private byte[] decodeBlock(byte[] in, int inOff, int inLen)
    +        throws InvalidCipherTextException
    +    {
    +        int strictBlockSize = engine.getOutputBlockSize();
    +        byte[] block = engine.processBlock(in, inOff, inLen);
    +
    +        boolean incorrectLength = useStrictLength & (block.length != strictBlockSize);
    +
    +        byte[] data = block;
    +        if (block.length < strictBlockSize)
    +        {
    +            data = blockBuffer;
    +        }
    +
    +        int plaintextLength = forPrivateKey ? checkPkcs1Encoding2(data) : checkPkcs1Encoding1(data);
    +
    +        try
    +        {
    +            if (plaintextLength < 0 | incorrectLength)
    +            {
    +                // Special behaviour to avoid throw/catch/throw in CipherSpi
    +                return null;
    +            }
    +
    +            byte[] result = new byte[plaintextLength];
    +            System.arraycopy(data, data.length - plaintextLength, result, 0, plaintextLength);
    +            return result;
    +        }
    +        finally
    +        {
    +            Arrays.fill(block, (byte)0);
    +            Arrays.fill(blockBuffer, 0, Math.max(0, blockBuffer.length - block.length), (byte)0);
    +        }
    +    }
    +}
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

0

No linked articles in our index yet.