001 // Copyright (C) 2002 IAIK
002 // https://jce.iaik.tugraz.at
003 //
004 // Copyright (C) 2003 - 2025 Stiftung Secure Information and
005 // Communication Technologies SIC
006 // https://sic.tech
007 //
008 // All rights reserved.
009 //
010 // Redistribution and use in source and binary forms, with or without
011 // modification, are permitted provided that the following conditions
012 // are met:
013 // 1. Redistributions of source code must retain the above copyright
014 // notice, this list of conditions and the following disclaimer.
015 // 2. Redistributions in binary form must reproduce the above copyright
016 // notice, this list of conditions and the following disclaimer in the
017 // documentation and/or other materials provided with the distribution.
018 //
019 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
020 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
021 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
022 // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
023 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
024 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
025 // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
026 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
027 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
028 // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
029 // SUCH DAMAGE.
030
031 // Copyright (C) 2002 IAIK
032 // https://sic.tech/
033 //
034 // Copyright (C) 2003 - 2025 Stiftung Secure Information and
035 // Communication Technologies SIC
036 // https://sic.tech/
037 //
038 // All rights reserved.
039 //
040 // This source is provided for inspection purposes and recompilation only,
041 // unless specified differently in a contract with IAIK. This source has to
042 // be kept in strict confidence and must not be disclosed to any third party
043 // under any circumstances. Redistribution in source and binary forms, with
044 // or without modification, are <not> permitted in any case!
045 //
046 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
047 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
048 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
049 // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
050 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
051 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
052 // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
053 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
054 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
055 // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
056 // SUCH DAMAGE.
057 //
058 // $Header: /IAIK-CMS/current/src/demo/cms/envelopedData/EncryptedContentInfoDemo.java 26 12.02.25 17:58 Dbratko $
059 // $Revision: 26 $
060 //
061
062 package demo.cms.envelopedData;
063
064 import iaik.asn1.ASN;
065 import iaik.asn1.ASN1Object;
066 import iaik.asn1.INTEGER;
067 import iaik.asn1.OCTET_STRING;
068 import iaik.asn1.ObjectID;
069 import iaik.asn1.SEQUENCE;
070 import iaik.asn1.structures.AlgorithmID;
071 import iaik.cms.EncryptedContentInfoStream;
072 import iaik.cms.EnvelopedDataStream;
073 import iaik.cms.KeyTransRecipientInfo;
074 import iaik.cms.RecipientInfo;
075 import iaik.cms.SecurityProvider;
076 import iaik.security.random.SecRandom;
077 import iaik.utils.Util;
078 import iaik.x509.X509Certificate;
079
080 import java.io.ByteArrayInputStream;
081 import java.io.ByteArrayOutputStream;
082 import java.io.IOException;
083 import java.io.InputStream;
084 import java.math.BigInteger;
085 import java.security.PrivateKey;
086 import java.security.SecureRandom;
087 import java.security.spec.AlgorithmParameterSpec;
088
089 import javax.crypto.KeyGenerator;
090 import javax.crypto.SecretKey;
091 import javax.crypto.spec.IvParameterSpec;
092 import javax.crypto.spec.RC2ParameterSpec;
093
094 import demo.keystore.CMSKeyStore;
095
096 /**
097 * This class demonstrates the EnvelopedDataStream/EncryptedContentInfoStream usages
098 * for algorithms that require a specific parameter handling.
099 * <p>
100 * All keys and certificates are read from a keystore created by the
101 * SetupCMSKeyStore program.
102 * <p>
103 * The following algorithms are demonstrated:
104 * <ul>
105 * <li>ARCFOUR: Variable-key-size stream cipher; no parameters to be sent
106 * <li>RC2_CBC: Variable-key-size block cipher; parameters as used by S/MIME:
107 * rc2ParamterVersion and IV; encoded as SEQUENCE:
108 * <pre>
109 * RC2-CBC parameter ::= SEQUENCE {
110 * rc2ParameterVersion INTEGER,
111 * iv OCTET STRING (8)}
112 *
113 * For the effective-key-bits of 40, 64, and 128, the
114 * rc2ParameterVersion values are 160, 120, 58 respectively.
115 * </pre>
116 * <li>CAST5_CBC: Feistel type block cipher with key sizes of 40-128 bit in 8 bit
117 * increments; parameters (RFC 2144):
118 * <pre>
119 * Parameters ::= SEQUENCE {
120 * iv OCTET STRING DEFAULT 0,
121 * keyLength INTEGER }
122 *
123 * </pre>
124 * </ul>
125 * This class shows how an EncryptedContentInfo is explicit created for encrypting
126 * the content and supplying it to an EnvelopedDataStream object. Note that IAIK-CMS
127 * also allows to use EnvelopedData(Stream) for algorithms like RC2, ARCFOUR or CAST
128 * without having the necessity of explicit key/parameter handling, see {@link
129 * RC2EnvelopedDataDemo RC2EnvelopedDataDemo} for an example. Note that the usage
130 * of algorithms like RC2 is deprecated but used here for this demo since it
131 * requires a specific parameter handling.
132 */
133 public class EncryptedContentInfoDemo {
134
135 // certificate of user 1
136 X509Certificate user1;
137 // private key of user 1
138 PrivateKey user1_pk;
139 // certificate of user 2
140 X509Certificate user2;
141 // private key of user 2
142 PrivateKey user2_pk;
143 // secure random number generator
144 SecureRandom random;
145
146 /**
147 * Setup the demo certificate chains.
148 *
149 * Keys and certificate are retrieved from the demo KeyStore.
150 *
151 * @throws IOException if an file read error occurs
152 */
153 public EncryptedContentInfoDemo() throws IOException {
154
155 System.out.println();
156 System.out.println("********************************************************************************************");
157 System.out.println("* EncryptedContentInfoDemo *");
158 System.out.println("* (shows the usage of the EncryptedContentInfo implementation for encrypting some message) *");
159 System.out.println("********************************************************************************************");
160 System.out.println();
161
162
163 // add all certificates to the list
164 X509Certificate[] certs = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_1);
165 user1 = certs[0];
166 user1_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_1);
167 user2 = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_2)[0];
168 user2_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_2);
169
170 random = SecRandom.getDefault();
171 }
172
173
174 /**
175 * Creates a CMS <code>EnvelopedDataStream</code> message.
176 *
177 * @param message the message to be enveloped, as byte representation
178 * @param contentEA the content encryption algorithm
179 * @param keyLength the key length for the symmetric key
180 * @return the DER encoding of the <code>EnvelopedData</code> object just created
181 * @throws Exception if the <code>EnvelopedData</code> object cannot be created
182 */
183 public byte[] createEnvelopedDataStream(byte[] message, AlgorithmID contentEA, int keyLength) throws Exception {
184
185 SecurityProvider provider = SecurityProvider.getSecurityProvider();
186 ByteArrayInputStream is = new ByteArrayInputStream(message);
187
188 AlgorithmParameterSpec params = null;
189 SecretKey secretKey = null;
190
191 // create iv
192 byte[] iv = new byte[8];
193 random.nextBytes(iv);
194
195 int rc2_param = 58;
196 if (contentEA.equals(AlgorithmID.rc2_CBC)) {
197
198 switch (keyLength) {
199 case 40:
200 rc2_param = 160;
201 break;
202 case 64:
203 rc2_param = 120;
204 break;
205 default: // 128
206 rc2_param = 58;
207 keyLength = 128;
208 }
209 // create the paramters (SEQUENCE) to be sent
210 SEQUENCE parameter = new SEQUENCE();
211 parameter.addComponent(new INTEGER(rc2_param));
212 parameter.addComponent(new OCTET_STRING(iv));
213 contentEA.setParameter(parameter);
214 params = new RC2ParameterSpec(keyLength,iv);
215 } else if (contentEA.equals(AlgorithmID.arcfour)){
216 // no params for ARCFOUR
217 params = null;
218 } else if (contentEA.equals(AlgorithmID.cast5_CBC)) {
219 SEQUENCE parameter = new SEQUENCE();
220 parameter.addComponent(new OCTET_STRING(iv));
221 parameter.addComponent(new INTEGER(keyLength));
222 contentEA.setParameter(parameter);
223 params = new IvParameterSpec(iv);
224
225 } else {
226 throw new Exception("Algorithm " + contentEA + " not supportted for this test!");
227 }
228
229 KeyGenerator keyGen = provider.getKeyGenerator(contentEA, keyLength);
230 // generate a new key
231 secretKey = keyGen.generateKey();
232
233 // create the EncryptedContentInfo for the content to be encrypted
234 EncryptedContentInfoStream eci = new EncryptedContentInfoStream(ObjectID.pkcs7_data, is);
235 // setup the cipher for encryption
236 eci.setupCipher(contentEA, secretKey, params);
237
238 // create the recipient infos
239 RecipientInfo[] recipients = new RecipientInfo[2];
240 // user1 is the first receiver
241 recipients[0] = new KeyTransRecipientInfo(user1, (AlgorithmID)AlgorithmID.rsaEncryption.clone());
242 // encrypt the secret key for recipient 1
243 recipients[0].encryptKey(secretKey);
244 // user2 is the second receiver
245 recipients[1] = new KeyTransRecipientInfo(user2, (AlgorithmID)AlgorithmID.rsaEncryption.clone());
246 // encrypt the secret key for recipient 2
247 recipients[1].encryptKey(secretKey);
248 // now create the EnvelopedDataStream
249 EnvelopedDataStream enveloped_data = new EnvelopedDataStream(recipients, eci);
250
251 // return the EnvelopedDate as DER encoded byte array with block size 2048
252 ByteArrayOutputStream os = new ByteArrayOutputStream();
253 enveloped_data.writeTo(os, 2048);
254 byte[] enc = os.toByteArray();
255 return enc;
256
257 }
258
259 /**
260 * Decrypts the encrypted content of the given CMS <code>EnvelopedData</code> object for the
261 * specified recipient and returns the decrypted (= original) message.
262 *
263 * @param encoding the <code>EnvelopedData</code> object as DER encoded byte array
264 * @param privateKey the private key to decrypt the message
265 * @param recipientInfoIndex the index into the <code>RecipientInfo</code> array
266 * to which the specified private key belongs
267 *
268 * @return the recovered message, as byte array
269 * @throws Exception if the message cannot be recovered
270 */
271 public byte[] getEnvelopedDataStream(byte[] encoding, PrivateKey privateKey, int recipientInfoIndex) throws Exception {
272
273 // create the EnvelopedData object from a DER encoded byte array
274 // we are testing the stream interface
275 ByteArrayInputStream is = new ByteArrayInputStream(encoding);
276 EnvelopedDataStream enveloped_data = new EnvelopedDataStream(is);
277
278 AlgorithmParameterSpec params = null;
279 // get the recipient infos
280 RecipientInfo[] recipients = enveloped_data.getRecipientInfos();
281
282 System.out.println("\nThis message can be decrypted by the owners of the following certificates:");
283
284 for (int i=0; i<recipients.length; i++) {
285 System.out.println("Recipient "+(i+1)+":");
286 System.out.println(recipients[i].getRecipientIdentifiers()[0]);
287 }
288 // decrypt symmetric content encryption key, e.g.:
289 SecretKey secretKey = recipients[recipientInfoIndex].decryptKey(user1_pk);
290
291 //get the ECI from the enveloped data:
292 EncryptedContentInfoStream eci = (EncryptedContentInfoStream)enveloped_data.getEncryptedContentInfo();
293 //get the content encryption algorithm:
294 AlgorithmID contentEA = eci.getContentEncryptionAlgorithm();
295 System.out.println("\nContent Encryption Algorithm: " + contentEA);
296 if (contentEA.equals(AlgorithmID.rc2_CBC)) {
297 // get the parameters as SEQUENCE
298 SEQUENCE seq = (SEQUENCE)contentEA.getParameter();
299 // the iv is the second component
300 OCTET_STRING oct = (OCTET_STRING)seq.getComponentAt(1);
301 // create an IvParameterSpec:
302 //params = new IvParameterSpec((byte[])oct.getValue());
303 int rc2ParameterVersion = ((BigInteger)seq.getComponentAt(0).getValue()).intValue();
304 int effective_key_bits = 32;
305 switch (rc2ParameterVersion) {
306 case 160:
307 effective_key_bits = 40;
308 break;
309 case 120:
310 effective_key_bits = 64;
311 break;
312 case 58:
313 effective_key_bits = 128;
314 break;
315 default:
316 throw new Exception("Invalid rc2ParameterVersion " + rc2ParameterVersion + "!");
317
318 }
319 params = new RC2ParameterSpec(effective_key_bits,(byte[])seq.getComponentAt(1).getValue());
320
321 }
322 else if (contentEA.equals(AlgorithmID.rc5_CBC)) {
323 OCTET_STRING oct = (OCTET_STRING)contentEA.getParameter();
324 // create an IvParameterSpec:
325 params = new IvParameterSpec((byte[])oct.getValue());
326 } else if (contentEA.equals(AlgorithmID.arcfour)) {
327 params = null;
328 } else if (contentEA.equals(AlgorithmID.cast5_CBC)) {
329 // get the parameters
330 ASN1Object asn1Params = contentEA.getParameter();
331 if (asn1Params.isA(ASN.SEQUENCE)) {
332 // the iv is the first component
333 params = new IvParameterSpec((byte[])asn1Params.getComponentAt(0).getValue());
334 } else {
335 // to be compatible with (invalid) CAST AlgorithmIDs only using the IV as parameters
336 params = new IvParameterSpec((byte[])asn1Params.getValue());
337 }
338 } else {
339 throw new Exception("Algorithm " + contentEA + " not supportted for this test!");
340 }
341
342
343 //now setup the cipher with previously decrypted recipient key amd params
344 eci.setupCipher(secretKey, params);
345 //get and read the data thereby actually performing the decryption
346 InputStream data_is = eci.getInputStream();
347 ByteArrayOutputStream baos = new ByteArrayOutputStream();
348 Util.copyStream(data_is, baos, null);
349 byte[] decrypted = baos.toByteArray();
350 return decrypted;
351
352 }
353
354
355 /**
356 * Starts the test.
357 */
358 public void start() {
359 // the test message
360 String m = "This is the test message.";
361 System.out.println("Test message: \""+m+"\"");
362 System.out.println();
363 byte[] message = m.getBytes();
364
365 try {
366 byte[] data;
367 byte[] received_message = null;
368
369
370 // the stream implementation
371 //
372 // test CMS EnvelopedDataStream
373 //
374
375 // ARCFOUR
376 System.out.println("\nEnvelopedDataStream demo for algorithm ARCFOUR [create]:\n");
377 data = createEnvelopedDataStream(message, (AlgorithmID)AlgorithmID.arcfour.clone(), 128);
378 // transmit data
379 System.out.println("\nEnvelopedDataStream demo [parse]:\n");
380 // user1 means index 0 (hardcoded for this demo)
381 received_message = getEnvelopedDataStream(data, user1_pk, 0);
382 System.out.print("\nDecrypted content: ");
383 System.out.println(new String(received_message));
384
385 // RC2
386 System.out.println("\nEnvelopedDataStream demo for algorithm RC2 [create]:\n");
387 data = createEnvelopedDataStream(message, (AlgorithmID)AlgorithmID.rc2_CBC.clone(), 128);
388 // transmit data
389 System.out.println("\nEnvelopedDataStream demo [parse]:\n");
390 // user1 means index 0 (hardcoded for this demo)
391 received_message = getEnvelopedDataStream(data, user1_pk, 0);
392 System.out.print("\nDecrypted content: ");
393 System.out.println(new String(received_message));
394
395 // CAST5_CBC
396 System.out.println("\nEnvelopedDataStream demo for algorithm CAST5_CBC [create]:\n");
397 data = createEnvelopedDataStream(message, (AlgorithmID)AlgorithmID.cast5_CBC.clone(), 128);
398 // transmit data
399 System.out.println("\nEnvelopedDataStream demo [parse]:\n");
400 // user1 means index 0 (hardcoded for this demo)
401 received_message = getEnvelopedDataStream(data, user1_pk, 0);
402 System.out.print("\nDecrypted content: ");
403 System.out.println(new String(received_message));
404
405 } catch (Exception ex) {
406 ex.printStackTrace();
407 throw new RuntimeException(ex.toString());
408 }
409 }
410
411 /**
412 * The main method.
413 *
414 * @throws IOException
415 * if an I/O error occurs when reading required keys
416 * and certificates from files
417 */
418 public static void main(String argv[]) throws Exception {
419
420 demo.DemoUtil.initDemos();
421
422 (new EncryptedContentInfoDemo()).start();
423 System.out.println("\nReady!");
424 System.in.read();
425 }
426 }