From 2c6b504d75f2a7beab51440925dbf9dbdb286dcd Mon Sep 17 00:00:00 2001 From: nquangit Date: Fri, 5 Jun 2026 12:37:36 +0700 Subject: [PATCH] Add configurable RSA-OAEP support to RSA operations The RSA Encryption/Decryption operations previously only did RSA/ECB/ PKCS1Padding in PEM mode, and in KeyStore mode relied on named OAEP transformations (e.g. OAEPWITHSHA-256ANDMGF1PADDING) which silently keep MGF1 at SHA-1 under SunJCE. Neither could reproduce a common client-side config such as forge's RSA-OAEP with SHA-256 for both the OAEP and MGF1 digests. Introduce RsaCipherBuilder, a small helper that builds an RSA Cipher from a padding plus, for OAEP, an explicit OAEPParameterSpec with independently selectable OAEP and MGF1 message digests. Both operations now expose 'Padding', 'OAEP Hash' and 'MGF1 Hash' dropdowns in PEM and KeyStore mode; the hash fields are shown only while OAEP is selected. Defaults stay on PKCS1Padding so existing recipes are unaffected. KeyStore padding choices are sourced from the provider but always include an OAEP option, since some JVMs (incl. Burp's bundled JRE) advertise a reduced RSA padding list that omits OAEP. Add RsaCipherBuilderTest covering OAEP round-trips, mixed digests, PKCS1 backward-compat and the KeyStore padding fallbacks. --- .../encryption/RsaCipherBuilder.java | 118 ++++++++++++++++++ .../operations/encryption/RsaDecryption.java | 89 ++++++++++++- .../operations/encryption/RsaEncryption.java | 89 ++++++++++++- .../encryption/RsaCipherBuilderTest.java | 111 ++++++++++++++++ 4 files changed, 397 insertions(+), 10 deletions(-) create mode 100644 src/main/java/de/usd/cstchef/operations/encryption/RsaCipherBuilder.java create mode 100644 src/test/java/de/usd/cstchef/operations/encryption/RsaCipherBuilderTest.java diff --git a/src/main/java/de/usd/cstchef/operations/encryption/RsaCipherBuilder.java b/src/main/java/de/usd/cstchef/operations/encryption/RsaCipherBuilder.java new file mode 100644 index 0000000..4c665b1 --- /dev/null +++ b/src/main/java/de/usd/cstchef/operations/encryption/RsaCipherBuilder.java @@ -0,0 +1,118 @@ +package de.usd.cstchef.operations.encryption; + +import java.security.Key; +import java.util.LinkedHashSet; + +import javax.crypto.Cipher; +import javax.crypto.spec.OAEPParameterSpec; +import javax.crypto.spec.PSource; + +import java.security.spec.MGF1ParameterSpec; + +/** + * Central place that knows how to turn a user selected RSA padding (and, for + * OAEP, the two message digests) into an initialised {@link Cipher}. + * + * Keeping this logic in one small class means the UI operations + * ({@code RsaEncryption} / {@code RsaDecryption}) only have to read their combo + * boxes and hand the values over. Supporting a new padding or digest in the + * future is a one line change to the constants below. + */ +public class RsaCipherBuilder { + + /** Selectable padding modes shown in the operations' "Padding" combo box. */ + public static final String PADDING_PKCS1 = "PKCS1Padding"; + public static final String PADDING_OAEP = "OAEPPadding"; + + public static final String[] PADDINGS = new String[] { + PADDING_PKCS1, + PADDING_OAEP + }; + + /** + * Message digests offered for the OAEP hash and the MGF1 hash. These are + * the digests SunJCE accepts inside an {@link OAEPParameterSpec}. Add new + * entries here to make them available in both RSA operations at once. + */ + public static final String[] DIGESTS = new String[] { + "SHA-1", + "SHA-224", + "SHA-256", + "SHA-384", + "SHA-512" + }; + + public static final String DEFAULT_DIGEST = "SHA-256"; + + /** + * Whether a padding name selects OAEP. Matches both the simple PEM choice + * ({@link #PADDING_OAEP}) and the provider's named variants used in KeyStore + * mode (e.g. {@code OAEPWITHSHA-256ANDMGF1PADDING}). + */ + public static boolean isOaep(String padding) { + return padding != null && padding.toUpperCase().contains("OAEP"); + } + + /** + * Build the padding list for KeyStore mode. We keep whatever the security + * provider advertises (so previously saved recipes still match) but make + * sure an OAEP option is always present. Some JVMs / Burp's bundled JRE + * advertise a reduced RSA padding list, which otherwise leaves the user + * with no way to pick OAEP even though we support it. + */ + public static String[] keyStorePaddings(String[] providerPaddings) { + LinkedHashSet paddings = new LinkedHashSet<>(); + if (providerPaddings != null) { + for (String padding : providerPaddings) { + if (padding != null && !padding.isEmpty()) { + paddings.add(padding); + } + } + } + + // Fall back to PKCS1 if the provider advertised nothing usable. + if (paddings.isEmpty()) { + paddings.add(PADDING_PKCS1); + } + + boolean hasOaep = false; + for (String padding : paddings) { + if (isOaep(padding)) { + hasOaep = true; + break; + } + } + if (!hasOaep) { + paddings.add(PADDING_OAEP); + } + + return paddings.toArray(new String[0]); + } + + /** + * Build and initialise an RSA {@link Cipher}. + * + * @param mode {@link Cipher#ENCRYPT_MODE} or {@link Cipher#DECRYPT_MODE} + * @param key the public (encrypt) or private (decrypt) key + * @param padding one of {@link #PADDINGS} + * @param oaepHash OAEP digest, one of {@link #DIGESTS} (ignored for PKCS1) + * @param mgf1Hash MGF1 digest, one of {@link #DIGESTS} (ignored for PKCS1) + */ + public static Cipher build(int mode, Key key, String padding, String oaepHash, String mgf1Hash) throws Exception { + if (PADDING_OAEP.equals(padding)) { + Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding"); + OAEPParameterSpec spec = new OAEPParameterSpec( + oaepHash, + "MGF1", + new MGF1ParameterSpec(mgf1Hash), + PSource.PSpecified.DEFAULT); + cipher.init(mode, key, spec); + return cipher; + } + + // Default / backwards compatible behaviour: RSA with PKCS#1 v1.5 padding. + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(mode, key); + return cipher; + } +} diff --git a/src/main/java/de/usd/cstchef/operations/encryption/RsaDecryption.java b/src/main/java/de/usd/cstchef/operations/encryption/RsaDecryption.java index 28e0de9..4274654 100644 --- a/src/main/java/de/usd/cstchef/operations/encryption/RsaDecryption.java +++ b/src/main/java/de/usd/cstchef/operations/encryption/RsaDecryption.java @@ -1,5 +1,6 @@ package de.usd.cstchef.operations.encryption; +import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.BufferedReader; @@ -28,6 +29,10 @@ public class RsaDecryption extends KeystoreOperation { private VariableTextArea privateKeyTextArea; + private JComboBox pemPadding; + private JComboBox pemOaepHash; + private JComboBox pemMgf1Hash; + private JComboBox typeComboBox; private static String[] inOutModes = new String[] { "Raw", "Hex", "Base64" }; @@ -38,6 +43,8 @@ public class RsaDecryption extends KeystoreOperation { protected JComboBox inputMode; protected JComboBox outputMode; protected JComboBox paddings; + protected JComboBox ksOaepHash; + protected JComboBox ksMgf1Hash; private String lastSelection = "PEM"; @@ -74,8 +81,10 @@ protected ByteArray perform(ByteArray input) throws Exception { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(keySpec); - Cipher cipher = Cipher.getInstance("RSA"); - cipher.init(Cipher.DECRYPT_MODE, privateKey); + Cipher cipher = RsaCipherBuilder.build(Cipher.DECRYPT_MODE, privateKey, + (String) pemPadding.getSelectedItem(), + (String) pemOaepHash.getSelectedItem(), + (String) pemMgf1Hash.getSelectedItem()); return factory.createByteArray(cipher.doFinal(input.getBytes())); } else if(typeComboBox.getSelectedItem().equals("KeyStore")) { @@ -83,8 +92,18 @@ else if(typeComboBox.getSelectedItem().equals("KeyStore")) { throw new IllegalArgumentException("No private key available."); String padding = (String)paddings.getSelectedItem(); - Cipher cipher = Cipher.getInstance(String.format("%s/%s/%s", algorithm, cipherMode, padding)); - cipher.init(Cipher.DECRYPT_MODE, this.selectedEntry.getPrivateKey()); + Cipher cipher; + if(RsaCipherBuilder.isOaep(padding)) { + // Named OAEP transformations (e.g. OAEPWITHSHA-256ANDMGF1PADDING) leave MGF1 at + // SHA-1 in SunJCE, so build an explicit OAEPParameterSpec with both chosen digests. + cipher = RsaCipherBuilder.build(Cipher.DECRYPT_MODE, this.selectedEntry.getPrivateKey(), + RsaCipherBuilder.PADDING_OAEP, + (String) ksOaepHash.getSelectedItem(), + (String) ksMgf1Hash.getSelectedItem()); + } else { + cipher = Cipher.getInstance(String.format("%s/%s/%s", algorithm, cipherMode, padding)); + cipher.init(Cipher.DECRYPT_MODE, this.selectedEntry.getPrivateKey()); + } String selectedInputMode = (String)inputMode.getSelectedItem(); String selectedOutputMode = (String)outputMode.getSelectedItem(); @@ -155,14 +174,35 @@ public void createMyUI() { CipherUtils utils = CipherUtils.getInstance(); CipherInfo info = utils.getCipherInfo(this.algorithm); - this.paddings = new JComboBox<>(info.getPaddings()); + this.paddings = new JComboBox<>(RsaCipherBuilder.keyStorePaddings(info.getPaddings())); this.addUIElement("Padding", this.paddings); + this.ksOaepHash = new JComboBox<>(RsaCipherBuilder.DIGESTS); + this.ksOaepHash.setSelectedItem(RsaCipherBuilder.DEFAULT_DIGEST); + this.addUIElement("OAEP Hash", this.ksOaepHash); + + this.ksMgf1Hash = new JComboBox<>(RsaCipherBuilder.DIGESTS); + this.ksMgf1Hash.setSelectedItem(RsaCipherBuilder.DEFAULT_DIGEST); + this.addUIElement("MGF1 Hash", this.ksMgf1Hash); + this.inputMode = new JComboBox<>(inOutModes); this.addUIElement("Input", this.inputMode); this.outputMode = new JComboBox<>(inOutModes); this.addUIElement("Output", this.outputMode); + + // The OAEP/MGF1 digests only apply to OAEP paddings, so only show them then. + this.paddings.addActionListener(e -> updateKeyStoreOaepVisibility()); + updateKeyStoreOaepVisibility(); + } + + private void updateKeyStoreOaepVisibility() { + boolean oaep = RsaCipherBuilder.isOaep((String) this.paddings.getSelectedItem()); + setRowVisible(this.ksOaepHash, oaep); + setRowVisible(this.ksMgf1Hash, oaep); + validate(); + repaint(); + updateStepPanel(); } private void clearUI() { @@ -182,6 +222,45 @@ private void clearUI() { private void createUIForPEM() { this.privateKeyTextArea = new VariableTextArea(); this.addUIElement("Private Key", this.privateKeyTextArea); + + this.pemPadding = new JComboBox<>(RsaCipherBuilder.PADDINGS); + this.addUIElement("Padding", this.pemPadding); + + this.pemOaepHash = new JComboBox<>(RsaCipherBuilder.DIGESTS); + this.pemOaepHash.setSelectedItem(RsaCipherBuilder.DEFAULT_DIGEST); + this.addUIElement("OAEP Hash", this.pemOaepHash); + + this.pemMgf1Hash = new JComboBox<>(RsaCipherBuilder.DIGESTS); + this.pemMgf1Hash.setSelectedItem(RsaCipherBuilder.DEFAULT_DIGEST); + this.addUIElement("MGF1 Hash", this.pemMgf1Hash); + + // The OAEP/MGF1 digests only apply to OAEP padding, so only show them then. + this.pemPadding.addActionListener(e -> updatePemOaepVisibility()); + updatePemOaepVisibility(); + } + + // Show the OAEP/MGF1 hash rows only while OAEP padding is selected. + private void updatePemOaepVisibility() { + boolean oaep = RsaCipherBuilder.PADDING_OAEP.equals(this.pemPadding.getSelectedItem()); + setRowVisible(this.pemOaepHash, oaep); + setRowVisible(this.pemMgf1Hash, oaep); + validate(); + repaint(); + updateStepPanel(); + } + + private void setRowVisible(Component comp, boolean visible) { + Component row = comp.getParent(); + Component[] comps = getContentBoxComponents(); + for (int i = 0; i < comps.length; i++) { + if (comps[i] == row) { + comps[i].setVisible(visible); + if (i + 1 < comps.length) { + comps[i + 1].setVisible(visible); // the spacing strut that follows the row + } + break; + } + } } public void updateStepPanel() { diff --git a/src/main/java/de/usd/cstchef/operations/encryption/RsaEncryption.java b/src/main/java/de/usd/cstchef/operations/encryption/RsaEncryption.java index 99d6f47..dd8f2db 100644 --- a/src/main/java/de/usd/cstchef/operations/encryption/RsaEncryption.java +++ b/src/main/java/de/usd/cstchef/operations/encryption/RsaEncryption.java @@ -14,6 +14,7 @@ import org.bouncycastle.util.encoders.Hex; +import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -29,6 +30,10 @@ public class RsaEncryption extends KeystoreOperation { private VariableTextArea publicKeyTextArea; + private JComboBox pemPadding; + private JComboBox pemOaepHash; + private JComboBox pemMgf1Hash; + private JComboBox typeComboBox; private static String[] inOutModes = new String[] { "Raw", "Hex", "Base64" }; @@ -39,6 +44,8 @@ public class RsaEncryption extends KeystoreOperation { protected JComboBox inputMode; protected JComboBox outputMode; protected JComboBox paddings; + protected JComboBox ksOaepHash; + protected JComboBox ksMgf1Hash; private String lastSelection = "PEM"; @@ -77,8 +84,10 @@ protected ByteArray perform(ByteArray input) throws Exception { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec); - Cipher cipher = Cipher.getInstance("RSA"); - cipher.init(Cipher.ENCRYPT_MODE, publicKey); + Cipher cipher = RsaCipherBuilder.build(Cipher.ENCRYPT_MODE, publicKey, + (String) pemPadding.getSelectedItem(), + (String) pemOaepHash.getSelectedItem(), + (String) pemMgf1Hash.getSelectedItem()); return factory.createByteArray(cipher.doFinal(input.getBytes())); } else if(typeComboBox.getSelectedItem().equals("KeyStore")) { @@ -86,8 +95,18 @@ else if(typeComboBox.getSelectedItem().equals("KeyStore")) { throw new IllegalArgumentException("No certificate available."); String padding = (String)paddings.getSelectedItem(); - Cipher cipher = Cipher.getInstance(String.format("%s/%s/%s", algorithm, cipherMode, padding)); - cipher.init(Cipher.ENCRYPT_MODE, this.cert.getPublicKey()); + Cipher cipher; + if(RsaCipherBuilder.isOaep(padding)) { + // Named OAEP transformations (e.g. OAEPWITHSHA-256ANDMGF1PADDING) leave MGF1 at + // SHA-1 in SunJCE, so build an explicit OAEPParameterSpec with both chosen digests. + cipher = RsaCipherBuilder.build(Cipher.ENCRYPT_MODE, this.cert.getPublicKey(), + RsaCipherBuilder.PADDING_OAEP, + (String) ksOaepHash.getSelectedItem(), + (String) ksMgf1Hash.getSelectedItem()); + } else { + cipher = Cipher.getInstance(String.format("%s/%s/%s", algorithm, cipherMode, padding)); + cipher.init(Cipher.ENCRYPT_MODE, this.cert.getPublicKey()); + } String selectedInputMode = (String)inputMode.getSelectedItem(); String selectedOutputMode = (String)outputMode.getSelectedItem(); @@ -158,14 +177,35 @@ public void createMyUI() { CipherUtils utils = CipherUtils.getInstance(); CipherInfo info = utils.getCipherInfo(this.algorithm); - this.paddings = new JComboBox<>(info.getPaddings()); + this.paddings = new JComboBox<>(RsaCipherBuilder.keyStorePaddings(info.getPaddings())); this.addUIElement("Padding", this.paddings); + this.ksOaepHash = new JComboBox<>(RsaCipherBuilder.DIGESTS); + this.ksOaepHash.setSelectedItem(RsaCipherBuilder.DEFAULT_DIGEST); + this.addUIElement("OAEP Hash", this.ksOaepHash); + + this.ksMgf1Hash = new JComboBox<>(RsaCipherBuilder.DIGESTS); + this.ksMgf1Hash.setSelectedItem(RsaCipherBuilder.DEFAULT_DIGEST); + this.addUIElement("MGF1 Hash", this.ksMgf1Hash); + this.inputMode = new JComboBox<>(inOutModes); this.addUIElement("Input", this.inputMode); this.outputMode = new JComboBox<>(inOutModes); this.addUIElement("Output", this.outputMode); + + // The OAEP/MGF1 digests only apply to OAEP paddings, so only show them then. + this.paddings.addActionListener(e -> updateKeyStoreOaepVisibility()); + updateKeyStoreOaepVisibility(); + } + + private void updateKeyStoreOaepVisibility() { + boolean oaep = RsaCipherBuilder.isOaep((String) this.paddings.getSelectedItem()); + setRowVisible(this.ksOaepHash, oaep); + setRowVisible(this.ksMgf1Hash, oaep); + validate(); + repaint(); + updateStepPanel(); } private void clearUI() { @@ -185,6 +225,45 @@ private void clearUI() { private void createUIForPEM() { this.publicKeyTextArea = new VariableTextArea(); this.addUIElement("Public Key", this.publicKeyTextArea); + + this.pemPadding = new JComboBox<>(RsaCipherBuilder.PADDINGS); + this.addUIElement("Padding", this.pemPadding); + + this.pemOaepHash = new JComboBox<>(RsaCipherBuilder.DIGESTS); + this.pemOaepHash.setSelectedItem(RsaCipherBuilder.DEFAULT_DIGEST); + this.addUIElement("OAEP Hash", this.pemOaepHash); + + this.pemMgf1Hash = new JComboBox<>(RsaCipherBuilder.DIGESTS); + this.pemMgf1Hash.setSelectedItem(RsaCipherBuilder.DEFAULT_DIGEST); + this.addUIElement("MGF1 Hash", this.pemMgf1Hash); + + // The OAEP/MGF1 digests only apply to OAEP padding, so only show them then. + this.pemPadding.addActionListener(e -> updatePemOaepVisibility()); + updatePemOaepVisibility(); + } + + // Show the OAEP/MGF1 hash rows only while OAEP padding is selected. + private void updatePemOaepVisibility() { + boolean oaep = RsaCipherBuilder.PADDING_OAEP.equals(this.pemPadding.getSelectedItem()); + setRowVisible(this.pemOaepHash, oaep); + setRowVisible(this.pemMgf1Hash, oaep); + validate(); + repaint(); + updateStepPanel(); + } + + private void setRowVisible(Component comp, boolean visible) { + Component row = comp.getParent(); + Component[] comps = getContentBoxComponents(); + for (int i = 0; i < comps.length; i++) { + if (comps[i] == row) { + comps[i].setVisible(visible); + if (i + 1 < comps.length) { + comps[i + 1].setVisible(visible); // the spacing strut that follows the row + } + break; + } + } } public void updateStepPanel() { diff --git a/src/test/java/de/usd/cstchef/operations/encryption/RsaCipherBuilderTest.java b/src/test/java/de/usd/cstchef/operations/encryption/RsaCipherBuilderTest.java new file mode 100644 index 0000000..e358667 --- /dev/null +++ b/src/test/java/de/usd/cstchef/operations/encryption/RsaCipherBuilderTest.java @@ -0,0 +1,111 @@ +package de.usd.cstchef.operations.encryption; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.Arrays; +import java.util.List; + +import javax.crypto.Cipher; + +import org.junit.Before; +import org.junit.Test; + +public class RsaCipherBuilderTest { + + private KeyPair keyPair; + + @Before + public void setUp() throws Exception { + KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); + generator.initialize(2048); + this.keyPair = generator.generateKeyPair(); + } + + private byte[] encrypt(byte[] plain, String padding, String oaepHash, String mgf1Hash) throws Exception { + Cipher cipher = RsaCipherBuilder.build(Cipher.ENCRYPT_MODE, keyPair.getPublic(), padding, oaepHash, mgf1Hash); + return cipher.doFinal(plain); + } + + private byte[] decrypt(byte[] enc, String padding, String oaepHash, String mgf1Hash) throws Exception { + Cipher cipher = RsaCipherBuilder.build(Cipher.DECRYPT_MODE, keyPair.getPrivate(), padding, oaepHash, mgf1Hash); + return cipher.doFinal(enc); + } + + // Reproduces the JS forge configuration: RSA-OAEP with SHA-256 for both the + // OAEP digest and the MGF1 digest. + @Test + public void oaepSha256RoundTrip() throws Exception { + byte[] plain = "Sup3rS3cr3tP@ssw0rd".getBytes("UTF-8"); + + byte[] enc = encrypt(plain, RsaCipherBuilder.PADDING_OAEP, "SHA-256", "SHA-256"); + byte[] dec = decrypt(enc, RsaCipherBuilder.PADDING_OAEP, "SHA-256", "SHA-256"); + + assertArrayEquals(plain, dec); + } + + // OAEP is randomised, so two encryptions of the same plaintext must differ. + @Test + public void oaepIsRandomised() throws Exception { + byte[] plain = "hello".getBytes("UTF-8"); + + byte[] enc1 = encrypt(plain, RsaCipherBuilder.PADDING_OAEP, "SHA-256", "SHA-256"); + byte[] enc2 = encrypt(plain, RsaCipherBuilder.PADDING_OAEP, "SHA-256", "SHA-256"); + + assertFalse(Arrays.equals(enc1, enc2)); + } + + // Independently selectable digests: SHA-1 OAEP digest with SHA-256 MGF1. + @Test + public void oaepMixedDigestsRoundTrip() throws Exception { + byte[] plain = "mixed-digests".getBytes("UTF-8"); + + byte[] enc = encrypt(plain, RsaCipherBuilder.PADDING_OAEP, "SHA-1", "SHA-256"); + byte[] dec = decrypt(enc, RsaCipherBuilder.PADDING_OAEP, "SHA-1", "SHA-256"); + + assertArrayEquals(plain, dec); + } + + // Backwards compatible default path still works. + @Test + public void pkcs1RoundTrip() throws Exception { + byte[] plain = "legacy".getBytes("UTF-8"); + + byte[] enc = encrypt(plain, RsaCipherBuilder.PADDING_PKCS1, null, null); + byte[] dec = decrypt(enc, RsaCipherBuilder.PADDING_PKCS1, null, null); + + assertArrayEquals(plain, dec); + } + + // A reduced provider list (no OAEP) must still yield an OAEP option. + @Test + public void keyStorePaddingsAddsOaepWhenMissing() { + List paddings = Arrays.asList( + RsaCipherBuilder.keyStorePaddings(new String[] { "PKCS1PADDING" })); + + assertTrue(paddings.contains("PKCS1PADDING")); + assertTrue(paddings.contains(RsaCipherBuilder.PADDING_OAEP)); + } + + // A provider that already advertises OAEP variants is left untouched (no duplicate generic entry). + @Test + public void keyStorePaddingsKeepsProviderOaep() { + List paddings = Arrays.asList(RsaCipherBuilder.keyStorePaddings( + new String[] { "NOPADDING", "PKCS1PADDING", "OAEPWITHSHA-256ANDMGF1PADDING" })); + + assertTrue(paddings.contains("OAEPWITHSHA-256ANDMGF1PADDING")); + assertFalse(paddings.contains(RsaCipherBuilder.PADDING_OAEP)); + } + + // An empty provider list still produces usable choices. + @Test + public void keyStorePaddingsHandlesEmpty() { + List paddings = Arrays.asList(RsaCipherBuilder.keyStorePaddings(new String[0])); + + assertTrue(paddings.contains(RsaCipherBuilder.PADDING_PKCS1)); + assertTrue(paddings.contains(RsaCipherBuilder.PADDING_OAEP)); + } +}