Appendix A Solutions to Exercises
Solutions
- Some colleagues come to you with a problem they are having using a JCE provider they have downloaded. They have installed the provider correctly in the jre/lib/ext area and added it to the configuration file in java.security , but they are getting a java.lang.SecurityException with the message Unsupported keysize or algorithm parameters . What have they forgotten?
They have forgotten to install the unrestricted policy files for the JCE. This happens rather a lot.
- You are running in a Windows environment. You have downloaded and installed everything into your JDK that is required to support the provider you want to use, but you still find that some Java applications you are using fail to find the provider, even though you are sure you have installed it. Why might this be happening?
The problem is most likely that the application is running out of the JRE installed on the Windows machine, not the JDK. You need to make sure that the JRE is configured the same way as the JDK with the right JAR files installed.
- You are attempting to use an algorithm that is available in a provider you have installed, but you are finding that when you create an object to use it via the getInstance() method, the object does not have all the capabilities that the documentation that comes with the provider indicates. Why might this be the case?
Chances are this is happening because the provider name has not been specified and, because of the precedence rules, some other provider is being used to create the object. Verify that getInstance() has been called with the provider name specified.
Solutions
- A colleague has written a program for decrypting a padded byte stream that was created by encrypting with a block cipher. For some reason the program the colleague has written is appending one or more zero bytes to the data created by decrypting the stream. What is the most likely reason for the extra bytes? How would you fix the program?
The key words here are "padded byte stream." This tells you that the extra zero bytes are most likely an artifact from the padding mechanism that was used to create the encrypted data. A look at the program will probably show that the program was written using the value returned from Cipher.getOutputSize() but does not take into account the fact that the number of bytes returned by Cipher.doFinal() can be less than that.
- You have written a program that is decrypting a block cipher encrypted stream created using CBC mode. For the most part, the data appears to be encrypting correctly, but the first block of the decrypted data is always wrong. What is the most likely cause of this?
The initialization vector, or IV, is probably incorrect. Remember, CBC mode is performed by XORing the previous block of ciphertext produced with the current block of input. In the case of the first block of input, the (nonexistent) block of cipher text is provided by the IV.
- If you have a Cipher object initialized in encryption mode that uses an IV, what are the two ways you can retrieve the IV's value?
You can use either Cipher.getIV(), which returns a simple byte array, or Cipher.getParameters() , which returns an AlgorithmParameters object.
- If you have a Cipher object that is using PBE-based encryption, how would you retrieve the parameters passed to the key generation function, other than the password?
You need to use Cipher.getParameters() and pass a PBEParameterSpec class to the AlgorithmParameters.getParameterSpec() method of the object returned by getParameters() .
- What is the most likely problem if data written through a CipherOutputStream appears to be truncated?
In the case of truncated data, the most likely problem is that CipherOutputStream.close() has not been called.
Solutions
- Why are message digests and MACs a necessary part of sending a message using encryption?
Encryption does not necessarily prevent an attacker from tampering with a message.
- You have been asked to implement a protocol that does not require encryption of the messages used in it, but it does require that the messages be tamperproof. How can you solve this problem while still allowing messages to be sent without encryption? What extra piece of information is now required when two parties want to communicate?
Use a MAC based on either a message digest or a cipher, whichever is mos convenient while being appropriate to the security needs of the data. The use of a MAC now means that two parties who want to communicate must be aware of the key material used to initialize the MAC.
- What is the primary limitation of the use of a MAC or message digest?
The primary limitation of a MAC or message digest is the amount of data that it is safe to feed into it. The data limitation exists because, beyond a certain size , the likelihood of different data computing to the same digest or MAC value becomes too high.
- What is wrong with the following code?
cipher.init(Cipher.ENCRYPT_MODE, key); String encrypted = new String(cipher.doFinal(input)); cipher.init(Cipher.DECRYPT_MODE, key); byte[] decrypted = cipher.doFinal(encrypted.getBytes());
What kind of String is created from new String(cipher.doFinal(input)) and what bytes are returned by encrypted.getBytes() is dependent on the default charset used by your Java runtime and will almost always mean that encrypted.getBytes() will not produce the same byte array that came out of cipher.doFinal(input) .
Solutions
- 1. A colleague is attempting to use RSA for key exchange and the implementation is failing whenever the leading byte of the key happens to be zero. What will be causing this problem? How do you fix it?
Leading zeros will go missing because the input data has to be converted to a big integer before it can be used with RSA. The solution is to use a padding mechanism like OAEP or PKCS #1.
- 2. The maximum amount of data that can be encrypted with RSA or El Gamal is normally limited by the size of the key, less any padding overhead that might exist. If you wanted to use either of these algorithms to help encrypt an arbitrarily large amount of data, how would you do it?
The answer is to combine the asymmetric algorithm with a symmetric one. Generate a random key for the symmetric algorithm and encrypt the data using it. Then encrypt the symmetric key using the public key for the person you want to send data to. The person can then use the private key to recover the symmetric key and then recover the data using the symmetric key.
- 3. Key agreement is different from key exchange in that it makes it possible for two or more people to arrive at the same key independently. What is the important thing to combine with a key agreement scheme if you are going to use one safely?
Key agreement schemes need to be combined with an authentication scheme to authenticate the keys being used; otherwise , the agreement scheme is open to a "man-in-the-middle" attack.
- 4. You saw previously that it was possible to use a MAC to authenticate data but that it had the disadvantage that it required a shared secret between all the parties wishing to check the same MAC. What asymmetric technique can you use instead that avoids this problem? What is it about it that makes it easier?
Digital signatures based on asymmetric algorithms are the best way of dealing with this. The feature of a digital signature is it has to be created using a person's private key, which should be known only to that person and it can be verified using the person's public key, which can be made freely available.
Solutions
- What happens to fields set to their default values when the ASN.1 structure that contains them is encoded using DER?
They are left out of the encoding.
- How would you implement the following ASN.1 type using the Bouncy Castle ASN.1 API?
MyChoice ::= CHOICE { message UTF8String, id INTEGER }
Here is one way of doing it. As you can see, the implementation reads a lot like a Java version of a C, or Pascal, union. Note the use of the ASN1Choice interface. Use of this will reduce the likelihood that this object is mistakenly tagged implicitly.
public class MyChoice extends ASN1Encodable implements ASN1Choice { ASN1Encodable value; public MyChoice(DERInteger value) { this.value = value; } public MyChoice(DERUTF8String value) { this.value = value; } public boolean isInteger() { return (value instanceof DERInteger); } public DERUTF8String getMessage() { if (isInteger()) { throw new IllegalStateException("not a message!"); } return (DERUTF8String)value; } public DERInteger getId() { if (isInteger()) { return (DERInteger)value; } throw new IllegalStateException("not an id!"); } public DERObject toASN1Object() { return value.toASN1Object(); } }
- What is meant by the word IMPLICIT in respect to a style of tagging? Think of a simple example of how it would be done using one of the classes representing an ASN.1 primitive in the Bouncy Castle API. What implication does the implicit style have for items that are derived from the CHOICE type?
Objects that are assigned a tag value using the implicit style have their own tag value overridden by the tag value assigned. In the Bouncy Castle API, this is done by setting the explicit parameter in the tagged object constructor to false . For example, the ASN.1 declaration
value [0] IMPLICIT INTEGER
would be created as a DER tagged value using the following:
DERTaggedObject t = new DERTaggedObject(false, derIntegerValue);
and then recovered using the following:
derIntegerValue = DERInteger.getInstance(t, false);
Remember too that if an object is already tagged, implicitly tagging it will remove the tag value. For this reason, as they commonly contain tagged values, any tag applied to an item of type CHOICE is applied explicitly.
- What are the two classes used to hold the DER encodings of public and private keys, respectively? What class is used to convert the encodings back into actual keys?
The X509EncodedKeySpec is used to contain public keys. The PKCS8EncodedKeySpec is used for carrying the encodings of private keys. The KeyFactory class is used to take the information in the encoded key specification objects and produce actual keys.
- What does an EncryptedPrivateKeyInfo object contain?
An EncryptedPrivateKeyInfo contains an encrypted encoding of a PKCS #8 PrivateKeyInfo object and the parameter and algorithm details that were used to do the encryption. On decryption, the recovered information is used to create a PKCS8EncodedKeySpec, which is then used to create a private key.
Solutions
- What is the biggest danger of trying to manipulate X.500 names as String objects in Java?
Some application software expects the X.500 name to have a specific encoding when it processes it, and converting from an encoded DN to String and back can lose this information. The reason this happens is that while several ASN.1 string types can be transformed into a Java String trivially, there is no way to determine which of those ASN.1 string types were used originally if you want to transform back the other way.
- How do you add an e-mail address for a certificate subject to a version 3 X.509 certificate?
You should add the e-mail address to a SubjectAlt Name extension attached to the certificate. Use the GeneralName choice item for the rfc822 Name.
- How do you request the CA to add a certificate extension when you issue a certification request?
Create an extensionRequest attribute and add an Extensions structure containing the extensions you want as the contents of the attribute value set. This attribute is then added to the attribute set that is carried with the certification request and processed by the CA.
- What is a certificate path , or chain? What roles do the root certificate and the end entity certificates play, respectively?
A certificate path, or chain, represents a linkage from an end entity certificate back to a root certificate where each certificate in the chain is signed by the next one after it. The root certificate is generally selfsigned and has to be accepted on trust. It is the certificate the others derive their validity from. The end entity certificate cannot be used to validate any other members of the chain and is the certificate you are trying to validate when the chain is examined.
- What is the easiest way to generate a particular type of encoding for a certificate path?
Convert the path, or chain, into a CertPath object and then use the CertPath.getEncoded() method requesting the format desired. You can find out which formats the CertPath objects of a particular provider can generate by calling CertPath.getEncodings() .
Solutions
- In an application where you are expecting a large number of unexpired revoked certificates to be present at any one time, as well as a large number of clients, what is the most appropriate certificate checking mechanism to deploy on the application clients : regular CRLs or OCSP?
OCSP. You are most likely to encounter problems with regular CRLs, as the size of the lists being distributed may easily become prohibitive, and having a large number of clients all trying to update their CRLs at the same time may result in severe performance degradation on the CRL server, with the usual unpleasant consequences.
- If you have to distribute CRLs in the case detailed in Exercise 1, what is one way you can reduce the cost of distributing updates? What is required to support this?
You can distribute deltas as updates, rather than sending out a new CRL. If you do this, the complete CRLs you send out need to have the CRLNumber extension present in them and the delta CRLs you send out need to use the DeltaCRLIndicator extension. Bear in mind that you need to have an algorithm, such as the one given in RFC 3280, for adding the deltas to the earlier complete CRL that was sent.
- How can you introduce validation of locally defined critical extensions into a PKIX CertPathValidator without having to change the underlying CertPathValidator provider?
Validation of private critical certificate extensions can be introduced to a PKIX CertPathValidator by using the PKIXCertPathChecker class. Remember that the checker you write must remove the extensions you are processing from the unresolved critical extensions Set thatis passed to the check() method for the validation to succeed.
- If you are using a PKIX CertPathBuilder to construct a certificate path for a given target certificate, what is the most straightforward way to construct the target constraints required for the path builder parameters?
Use the issuer and serial number of the target certificate, because these should always uniquely identify the certificate. For example, given the X509Certificate targetCert that is the target of the path build, the target constraints would be defined as
X509CertSelector targetConstraints = new X509CertSelector(); targetConstraints.setSerialNumber( targetCert .getSerialNumber()); targetConstraints.setIssuer( targetCert .getIssuerX500Principal().getEncoded());
Of course, if you are fortunate enough to be using JDK 1.5 or later, you can leave off the getEncoded() .
Solutions
- What available keystore types support the storing of symmetric keys?
Of the standard Java keystore types, the JCEKS supports storing symmetric keys. The Bouncy Castle provider also supports symmetric key storage with the BKS and UBER KeyStore type.
- What is one important thing to do if you are relying on the integrity of the JVM's cacerts file to help secure your application?
Change the default password!
- You have imported a PKCS #12 file into a Java application, but there doesn't appear to be an alias for the key entry you are looking for, or it is just appearing as some random string of hex. What is most likely to be the issue with the PKCS #12 file?
There is no "friendly name " attribute attached to the SafeBag containing the private key. If the file does import and you see a hex string, chances are it will be the value of the local key ID. Some applications stillinsist and having the "friendly name" attribute provided in order to successfully import a PKCS #12 file, so this can sometimes be a cause of failure as well.
- You have generated a PKCS #12 file using a Java application you have written, but you are unable to import it into another third-party application. What are the three most likely problems with the PKCS #12 file you have generated?
The first one is that the PKCS #12 store you are using may have been saved as a BER file, but the application can only import DER files ”you'll need to convert the file. The second one is the PKCS #12 store you are using may allow you to have an integrity password that is different from the privacy password, and the application can deal only with files that have the same password for both integrity and privacy. The third, and last, one is that the application may expect the certificates, other than the trust anchor, making up the validation path for the key's certificate to have the AuthorityKeyIdentifier and SubjectKeyIdentifier extensions present, and if it cannot find them, it is unable to build the validation path and accept the key as usable.
- Using the keytool , the org.bouncycastle.openssl.PEMReader class, the Utils class, and the Chapter 6 examples "Try It Out: Creating a Certificate from a Certification Request" and "Try It Out: Writing a CertPath" as helpers, show how to create a certificate request using the keytool for a keystore key entry, create a PKCS7 encoded certificate chain in response to the request, and update the key entry with the chain.
Most of the code for this you can cut and paste, but I'll reproduce one solution here. The first thing I'll do is provide the Java code and then just go through the command sequence.
Here's the Java code for parsing the request and creating the certificate response. It reads the request from a file called pkcs10.req and generates the certificate path making up the response in PKCS7 format in a file called pkcs7.pth .
package chapter8; import java.io.*; import java.math.BigInteger; import java.security.cert.*; import java.util.*; import javax.security.auth.x500.X500PrivateCredential; import org.bouncycastle.asn1.x509.*; import org.bouncycastle.jce.PKCS10CertificationRequest; import org.bouncycastle.openssl.PEMReader; import org.bouncycastle.x509.X509V3CertificateGenerator; import org.bouncycastle.x509.extension.*; /** * Example showing the processing of a PEM encoded PKCS #10 encoded request * in a file called "pkcs10.req". A PKCS7 certificate path is generated as a * response in the file "pkcs7.pth". *
* The certificate and its chain will be valid for 50 seconds. */ public class CertReqSolution { public static void main(String[] args) throws Exception { // create the CA certificates X500PrivateCredential rootCredential = Utils.createRootCredential(); X500PrivateCredential interCredential = Utils.createIntermediateCredential( rootCredential.getPrivateKey(), rootCredential.getCertificate()); // parse the request PEMReader pRd = new PEMReader( new InputStreamReader( new FileInputStream("pkcs10.req"))); PKCS10CertificationRequest request = (PKCS10CertificationRequest)pRd.readObject(); // get our validation certificate X509Certificate caCert = interCredential.getCertificate(); X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis())); certGen.setIssuerDN(caCert.getSubjectX500Principal()); certGen.setNotBefore(new Date(System.currentTimeMillis())); certGen.setNotAfter(new Date(System.currentTimeMillis() + 50000)); certGen.setSubjectDN(request.getCertificationRequestInfo().getSubject()); certGen.setPublicKey(request.getPublicKey("BC")); certGen.setSignatureAlgorithm("SHA256WithRSAEncryption"); // provide some basic extensions and mark the certificate // as appropriate for signing and encipherment certGen.addExtension( X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(caCert)); certGen.addExtension( X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure( request.getPublicKey("BC"))); certGen.addExtension( X509Extensions.BasicConstraints, true, new BasicConstraints(false)); certGen.addExtension( X509Extensions.KeyUsage, true, new KeyUsage( KeyUsage.digitalSignature KeyUsage.keyEncipherment)); // create the chain List chain = Arrays.asList( new Certificate[] { certGen.generateX509Certificate( interCredential.getPrivateKey(), "BC"), interCredential.getCertificate(), rootCredential.getCertificate() }); // create the CertPath CertificateFactory fact = CertificateFactory.getInstance("X.509", "BC"); CertPath path = fact.generateCertPath(chain); // write it out FileOutputStream fOut = new FileOutputStream("pkcs7.pth"); fOut.write(path.getEncoded("PKCS7")); fOut.close(); } }
There's nothing new in this code, although if you do find some of it confusing, it would be worth having another look at the examples and descriptions in Chapter 6 before going any further. Compile up the code into your class hierarchy and you're ready to proceed.
Now to use it with the keytool ”I've used test.jks as the keystore file here so that the commands avoid touching your .keystore file. Remember, both the java code you are using and the keytool will be operating on the current directory.
First, generate a key to use, responding appropriately to the questions the keytool prompts you with:
keytool -genkey -alias testKey -keystore test.jks -storepass testStore -keypass testKey
Next, generate the certification request into the file pkcs10.req :
keytool -certreq -alias testKey -keystore test.jks -storepass testStore -keypass testKey -file pkcs10.req
Next, run the Java program to read the request and generate the response:
java -cp your_class_hierarchy chapter8.CertReqSolution
where your_class_hierarchy is wherever you compiled the class file to.
Finally, import the certificate response:
keytool -import -alias testKey -keystore test.jks -storepass testStore -keypass testKey -file pkcs7.pth
You will be prompted to determine whether you want to trust the root certificate. Respond with a yes and you're done. Do a
keytool -list -v -keystore test.jks -storepass testStore
and you should see the certificate path has now been added to the entry.
Solutions
- The CMSProcessable interface is for the purpose of allowing the implementation of objects that write byte data to a CMS object for processing. How would you implement one that takes java.io.File objects?
Here's one way of doing it:
package chapter9; import java.io.*; import org.bouncycastle.cms.*; /** * CMSProcessable that handles File objects. */ public class CMSProcessableFile implements CMSProcessable { private File file; private static final int BUF_SIZE = 4096; /** * Base constructor. * * @param file a File object representing the file we want processed . */ public CMSProcessableFile(File file) { this.file = file; } /** * Write the contents of the file to the passed in OutputStream * * @param out the OutputStream passed in by the CMS API. */ public void write(OutputStream out) throws IOException, CMSException { FileInputStream fIn = new FileInputStream(file); byte[] buf = new byte[BUF_SIZE]; int count = 0; while ((count = fIn.read(buf)) > 0) { out.write(buf, 0, count); } fIn.close(); } /** * Return the File object we were created with. */ public Object getContent() { return file; } }
One thing to note: Be sure to close the InputStream you are using in the write() method, as I've done here. The write() method can be called multiple times.
- What is the best policy to adopt with the creation of certificates for encryption and signature validation?
Make them purpose-built. That means a certificate is for one purpose only and the public key will be used either for encryption or signature validation. There is an interesting slant on this in Chapter 13 of Practical Cryptography, where the authors point out that for a key to be different, just the public exponent needs to change; the modulus can be reused.
- Under what circumstances will a CMS signeddata message not contain any signers?
A CMS signeddata message will not contain any signers if it is being used only to carry certificates and/or CRLs. Messages of this type are called certificate management messages and are also created when Cyou encode a CertPath by passing the String to "PKCS7" to the CertPath object's getEncoded() method.
- How would you modify the SignedMailExample.createMultipartWithSignature() method so that it takes a MimeMultipart and signs it rather than a MimeBodyPart?
Strictly speaking, you don't have to! Here is the modified method with the changes highlighted:
public static MimeMultipart createMultipartWithSignature( PrivateKey key, X509Certificate cert, CertStore certsAndCRLs, MimeMultipart multiPart) throws Exception { // create some smime capabilities in case someone wants to respond ASN1EncodableVector signedAttrs = new ASN1EncodableVector(); SMIMECapabilityVector caps = new SMIMECapabilityVector(); caps.addCapability(SMIMECapability.aES256_CBC); caps.addCapability(SMIMECapability.dES_EDE3_CBC); caps.addCapability(SMIMECapability.rC2_CBC, 128); signedAttrs.add(new SMIMECapabilitiesAttribute(caps)); signedAttrs.add(new SMIMEEncryptionKeyPreferenceAttribute( SMIMEUtil.createIssuerAndSerialNumberFor(cert))); // set up the generator SMIMESignedGenerator gen = new SMIMESignedGenerator(); gen.addSigner(key, cert, SMIMESignedGenerator.DIGEST_SHA256, new AttributeTable(signedAttrs), null); gen.addCertificatesAndCRLs(certsAndCRLs); MimeBodyPart dataPart = new MimeBodyPart(); dataPart.setContent(multiPart); // create the signed message return gen.generate(dataPart, "BC"); }
As you can see, this could have been more easily achieved by wrapping multiPart before passing it to the method and leaving the method's parameter list alone.
- If you wrap a mail message in another MIME body part, sign the result, and then find another application that will not validate the signature, what is most likely to be the problem?
The other application is probably ignoring some of the headers in the mail message when it is doing the signature calculation. Typical headers that are ignored (when it happens) are "Reply-To," "From," and "To."
- When you are mixing signing and compression, under what circumstances is it mandatory that the signatures are calculated for the data after it has been compressed rather than before compression?
If you are using a "lossy" compression technique, you have to sign the compressed data rather than the original data. Because this compression technique involves some loss of information, signatures calculated on the original data will not match the results of the verification process at the other end on the reduced data.
Solutions
- What system properties are available to configure the key and trust managers for default SSL socket and server socket factories? Which of these properties has default values and what are they?
The key manager properties are javax.net.ssl.keyStore, javax.net.ssl.keyStorePassword, javax.net.ssl.keyStoreProvider, and javax.net.ssl.keyStoreType . The trust manager properties are javax.net.ssl.trustStore, javax.net.ssl.trustStorePassword, javax.net.ssl.trustStoreProvider, and javax.net.ssl.trustStoreType .
If the javax.net.ssl.trustStore property is not set, the system will look in the JVM's security directory for the file jssecacerts . If jssecacerts does not exist, the system will use the file cacerts .
If the javax.net.ssl.keyStoreType or javax.net.ssl.trustStoreType properties are not set, they default to the return value of KeyStore.getDefaultType() .
- Most Java keystore types allow access to trusted certificate entries without the need to specify a password for loading the keystore file. Why is it important to use a password when loading a keystore with trusted certificate entries?
Providing the password will force an integrity check on the file and help detect any tampering that might have occurred.
- What class do you use to provide your own KeyManager and TrustManager objects and create your own SSLServerSocketFactory and SSLSocketFactory objects that will be configured using them?
The javax.net.ssl.SSLContext class.
- Usually, an SSL handshake is done when one of the streams involved in an SSL link is written to. This might mean that exceptions thrown during handshaking are hidden beneath other exceptions. What method do you need to use to see explicit handshake exceptions when an SSL connection is initiated?
The SSLSocket.startHandshake() method.