Sorry about the red box, but we really need you to update your browser. Read this excellent article if you're wondering why we are no longer supporting this browser version. Go to Browse Happy for browser suggestions and how to update.

Elements of a cryptographic implementation

To create an app that uses the RIM Cryptographic API, you work with cryptographic elements such as keys, key stores, and certificates.

Cryptosystems

A cryptosystem is a set of algorithms that implements encryption, decryption, and key generation. Cryptosystems are used by all the public key cryptographic algorithms to hold public information that is common to all users within a cryptographic community or system. Typically the cryptosystem holds some mathematical numbers that each user's computer must have for the cryptographic calculations to succeed.

The RIM Cryptographic API supports the following cryptosystems: Diffie-Hellman, DSA, Elliptic Curve, Key Exchange Algorithm, and RSA.

To create a cryptosystem, you implement a class in the net.rim.device.api.crypto.CryptoSystem interface. The CryptoSystem interface simplifies the creation and management of keys, and lets you specify all possible parameters for keys.

RSA cryptosystem

RSA is a public key cryptosystem. The RSA cryptosystem is generally considered to be the easiest of the supported cryptosystems to use because you only need to set the size of the modulus. To create an RSA cryptosystem, use the RSACryptoSystem class.

The following code sample uses the RSACryptoSystem class to create an RSA cryptosystem. It specifies the modulusBitLength parameter as 1024.

RSACryptoSystem rsaCryptoSystem = new RSACryptoSystem(1024);

DSA cryptosystem

DSA is a United States federal government standard defined in FIPS 186. To create a DSA cryptosystem, use the DSACryptoSystem class.

There are multiple constructors that you can use to create a DSA cryptosystem. This is the simplest way to create a DSA cryptosystem:

DSACryptoSystem system = new DSACryptoSystem();

Alternatively, you can specify parameters. In the following example, the constructor is called with values for p, q, and g.

/* 1024 bit key parameters */
private static final byte[] p = new byte[] {
(byte)0xfd, (byte)0x7f, (byte)0x53, (byte)0x81, (byte)0x1d, (byte)0x75,
(byte)0x12, (byte)0x29, (byte)0x52, (byte)0xdf, (byte)0x4a, (byte)0x9c,
(byte)0x2e, (byte)0xec, (byte)0xe4, (byte)0xe7, (byte)0xf6, (byte)0x11,
(byte)0xb7, (byte)0x52, (byte)0x3c, (byte)0xef, (byte)0x44, (byte)0x00,
(byte)0xc3, (byte)0x1e, (byte)0x3f, (byte)0x80, (byte)0xb6, (byte)0x51,
(byte)0x26, (byte)0x69, (byte)0x45, (byte)0x5d, (byte)0x40, (byte)0x22,
(byte)0x51, (byte)0xfb, (byte)0x59, (byte)0x3d, (byte)0x8d, (byte)0x58,
(byte)0xfa, (byte)0xbf, (byte)0xc5, (byte)0xf5, (byte)0xba, (byte)0x30,
(byte)0xf6, (byte)0xcb, (byte)0x9b, (byte)0x55, (byte)0x6c, (byte)0xd7,
(byte)0x81, (byte)0x3b, (byte)0x80, (byte)0x1d, (byte)0x34, (byte)0x6f,
(byte)0xf2, (byte)0x66, (byte)0x60, (byte)0xb7, (byte)0x6b, (byte)0x99,
(byte)0x50, (byte)0xa5, (byte)0xa4, (byte)0x9f, (byte)0x9f, (byte)0xe8,
(byte)0x04, (byte)0x7b, (byte)0x10, (byte)0x22, (byte)0xc2, (byte)0x4f,
(byte)0xbb, (byte)0xa9, (byte)0xd7, (byte)0xfe, (byte)0xb7, (byte)0xc6,
(byte)0x1b, (byte)0xf8, (byte)0x3b, (byte)0x57, (byte)0xe7, (byte)0xc6,
(byte)0xa8, (byte)0xa6, (byte)0x15, (byte)0x0f, (byte)0x04, (byte)0xfb,
(byte)0x83, (byte)0xf6, (byte)0xd3, (byte)0xc5, (byte)0x1e, (byte)0xc3,
(byte)0x02, (byte)0x35, (byte)0x54, (byte)0x13, (byte)0x5a, (byte)0x16,
(byte)0x91, (byte)0x32, (byte)0xf6, (byte)0x75, (byte)0xf3, (byte)0xae,
(byte)0x2b, (byte)0x61, (byte)0xd7, (byte)0x2a, (byte)0xef, (byte)0xf2,
(byte)0x22, (byte)0x03, (byte)0x19, (byte)0x9d, (byte)0xd1, (byte)0x48,
(byte)0x01, (byte)0xc7
};
private static final byte[] q = new byte[] {
(byte)0x97, (byte)0x60, (byte)0x50, (byte)0x8f, (byte)0x15, (byte)0x23,
(byte)0x0b, (byte)0xcc, (byte)0xb2, (byte)0x92, (byte)0xb9, (byte)0x82,
(byte)0xa2, (byte)0xeb, (byte)0x84, (byte)0x0b, (byte)0xf0, (byte)0x58,
(byte)0x1c, (byte)0xf5
};
private static final byte[] g = new byte[] {
(byte)0xf7, (byte)0xe1, (byte)0xa0, (byte)0x85, (byte)0xd6, (byte)0x9b,
(byte)0x3d, (byte)0xde, (byte)0xcb, (byte)0xbc, (byte)0xab, (byte)0x5c,
(byte)0x36, (byte)0xb8, (byte)0x57, (byte)0xb9, (byte)0x79, (byte)0x94,
(byte)0xaf, (byte)0xbb, (byte)0xfa, (byte)0x3a, (byte)0xea, (byte)0x82,
(byte)0xf9, (byte)0x57, (byte)0x4c, (byte)0x0b, (byte)0x3d, (byte)0x07,
(byte)0x82, (byte)0x67, (byte)0x51, (byte)0x59, (byte)0x57, (byte)0x8e,
(byte)0xba, (byte)0xd4, (byte)0x59, (byte)0x4f, (byte)0xe6, (byte)0x71,
(byte)0x07, (byte)0x10, (byte)0x81, (byte)0x80, (byte)0xb4, (byte)0x49,
(byte)0x16, (byte)0x71, (byte)0x23, (byte)0xe8, (byte)0x4c, (byte)0x28,
(byte)0x16, (byte)0x13, (byte)0xb7, (byte)0xcf, (byte)0x09, (byte)0x32,
(byte)0x8c, (byte)0xc8, (byte)0xa6, (byte)0xe1, (byte)0x3c, (byte)0x16,
(byte)0x7a, (byte)0x8b, (byte)0x54, (byte)0x7c, (byte)0x8d, (byte)0x28,
(byte)0xe0, (byte)0xa3, (byte)0xae, (byte)0x1e, (byte)0x2b, (byte)0xb3,
(byte)0xa6, (byte)0x75, (byte)0x91, (byte)0x6e, (byte)0xa3, (byte)0x7f,
(byte)0x0b, (byte)0xfa, (byte)0x21, (byte)0x35, (byte)0x62, (byte)0xf1,
(byte)0xfb, (byte)0x62, (byte)0x7a, (byte)0x01, (byte)0x24, (byte)0x3b,
(byte)0xcc, (byte)0xa4, (byte)0xf1, (byte)0xbe, (byte)0xa8, (byte)0x51,
(byte)0x90, (byte)0x89, (byte)0xa8, (byte)0x83, (byte)0xdf, (byte)0xe1,
(byte)0x5a, (byte)0xe5, (byte)0x9f, (byte)0x06, (byte)0x92, (byte)0x8b,
(byte)0x66, (byte)0x5e, (byte)0x80, (byte)0x7b, (byte)0x55, (byte)0x25,
(byte)0x64, (byte)0x01, (byte)0x4c, (byte)0x3b, (byte)0xfe, (byte)0xcf,
(byte)0x49, (byte)0x2a
};
DSACryptoSystem system = new DSACryptoSystem( p, q, g );

Keys

Asymmetric key cryptography uses two keys, a public key to encrypt and verify input, and a private key to decrypt data. Symmetric key cryptography uses a single key for both encryption and decryption.

In the RIM Cryptographic API, you create keys using subinterfaces of the net.rim.device.api.crypto.Key interface. Here are some of the subinterfaces of the Key interface:

  • PublicKey: Public keys are used in asymmetric key cryptography. The public key is typically used in operations such as encryption and verification. You can divulge the contents of this key to anyone.

  • PrivateKey: Private keys are used in asymmetric key cryptography. The private key is typically used in operations such as decryption and signing. You must protect the contents of this key.

  • SymmetricKey: Symmetric keys are used in symmetric key cryptography. The symmetric key is used for both encryption and decryption. You must protect the contents of this key.

Code sample: Creating a symmetric key

The following example shows how to create a symmetric DES key object containing a randomly generated DES key. The DESKey class is in the SymmetricKey interface.

DESKey unknownKey = new DESKey();

Code sample: Creating a public key and private key

The following example creates a pair of keys: a public key and private key. It uses the default value for the public exponent, e. The value chosen for e is a compromise between efficiency and security.

// Create an RSA key pair.
RSAKeyPair rsaKeyPair = new RSAKeyPair( rsaCryptoSystem );

// Get the private and public keys that were just created.
RSAPublicKey rsaPublicKey = rsaKeyPair.getRSAPublicKey();
RSAPrivateKey rsaPrivateKey = rsaKeyPair.getRSAPrivateKey();

The following example creates assymetric keys, but you can specify the values for e and n.

// Create an RSA key pair with a modulus 1024 bits in length and values e and n.
RSACryptoSystem rsaCryptoSystem = new RSACryptoSystem(1024);
RSAPublicKey rsaPublicKey = new RSAPublicKey( rsaCryptoSystem, e, n );

Key stores

A key store is a collection of KeyStoreData records. A KeyStoreData record contains data such as public keys, private keys, symmetric keys, certificates, an identifier for the KeyStoreData record, and other items.

Each BlackBerry smartphone has a key store that is preloaded with root certificates for various certificate authorities. These trusted root certificates form the basis for all subsequent chains of trust. Smartphone users can customize their key store. For example, they can decide to be prompted for their password every time the key store is accessed.

The functionality for creating and managing key stores, including some predefined key stores, is provided in the net.rim.device.api.crypto.keystore.KeyStore package. The KeyStore package provides a key store model that allows you to manage the distribution and access of keys both on the desktop and on the smartphone. The KeyStore package contains the following classes:

  • The RIMKeyStore class is provided as a default implementation. It stores keys only until the smartphone is restarted, so it cannot be used to store permanent keys. An example of its use is to store smartcard keys: in that case, keys are always stored on the smartcard so the key store does not need to be persisted. The following key stores are variants of this class:

    • The PersistableRIMKeyStore class is useful for keys that need to persist across smartphone restarts.
    • The SyncableRIMKeyStore class is useful for keys that need to persist across restarts, and also allows the smartphone user to synchronize the keys with the Synchronize Certificates tool in the BlackBerry Desktop Manager (for BlackBerry Desktop Manager 5 and earlier).
  • The TrustedKeyStore class provides secure storage for certificates and keys that have a high security level. Only signed apps can add keys to the TrustedKeyStore.

  • The DeviceKeyStore provides functionality to synchronize with desktop software.

  • The CombinedKeyStore allows you to combine several key stores into what looks like one key store.

Code sample: Creating a key store

The following code sample creates a new key store called "My Keystore" and a key pair called keyPair. An associatedData object is created to help with later retrieval from the key store.

RIMKeyStore keyStore = new RIMKeyStore("My Keystore");
DSAKeyPair keyPair = new DSAKeyPair( new DSACryptoSystem() );
AssociatedData associatedData = 
   new AssociatedData( AssociatedData.EMAIL, "user01@example.com".getBytes() );

The next code sample adds keyPair to the key store. The set method adds a KeyStoreData object that contains information that will be associated with the array of aliases contained inside the class. The keyStore.set method is called with eight parameters:

  • The AssociatedData object (an associated Data array containing the aliases to index this record in the key store).

  • A label ("Crypto Signing Key") that identifies the key.

  • The private key.

  • The private key encoding algorithm. In this case it is set to null and so the default format, PKCS8, is used.

  • A low security level for the key, which means that when someone tries to access the private key, they will not be prompted for a password.

  • The public key.

  • The KeyUsage settings (in this case, these specify that the public key is for email protection and key agreement).
  • The ticket, which in this case is null.

A try/catch block is used to capture any exceptions that might be thrown.

try 
{
    keyStore.set( new AssociatedData[] {associatedData}, "Crypto Signing Key", 
                  keyPair.getDSAPrivateKey(), null, KeyStore.SECURITY_LEVEL_LOW,
                  keyPair.getDSAPublicKey(), 
                  KeyUsage.EMAIL_PROTECTION | KeyUsage.KEY_AGREEMENT, null );
}
catch( NoSuchAlgorithmException e ) 
{

} 
catch( KeyStoreCancelException e ) 
{
    return;
} 
catch( InvalidKeyEncodingException e ) 
{

} 
catch( CryptoTokenException e ) 
{

} 
catch( InvalidKeyException e ) 
{
} 
catch( CryptoUnsupportedOperationException e ) 
{

}

The following code sample retrieves the key called keyPair from the key store. First, the code creates an index. You only add an index to a key store once. Earlier in this example, an AssociatedData object was associated with the email address user01@example.com, so the new index is of type AssociatedDataKeyStoreIndex. The index is added to the key store. Next, the KeyStore.elements method is used to retrieve the key. The elements method uses the index (index.getID) and the bytes associated with the AssociatedData object ("user01@example.com".getBytes). The elements method returns an enumeration of all KeyStoreData objects that match the search criteria. A while loop is used to loop through the enumeration, looking for the label "Crypto Signing Key". (In a real world app, two keys might have the same AssociatedData information and the same label, so you should prompt the user to determine which key should be retrieved.) The getPrivateKey method is called to retrieve the key. The parameter null is used because there is no ticket.

AssociatedDataKeyStoreIndex index = 
        new AssociatedDataKeyStoreIndex( AssociatedData.EMAIL );

keyStore.addIndex( index );

Enumeration enum = 
        keyStore.elements( index.getID(), "user01@example.com".getBytes() );

KeyStoreData data = null;
PrivateKey privateKey = null;
while( enum.hasMoreElements() ) 
    {
    data = (KeyStoreData)enum.nextElement();
    if( "Crypto Signing Key".equals( data.getLabel() ) ) 
          {
          try 
               {
               privateKey = data.getPrivateKey( null );
               } 
          catch( NoSuchAlgorithmException e ) 
          {
          } 
          catch( KeyStoreCancelException e ) 
          {
          } 
          catch( InvalidKeyEncodingException e ) 
          {
          } 
          catch( CryptoTokenException e ) 
          {
          } 
          catch( CryptoUnsupportedOperationException e ) 
          {
          } 
          catch( KeyStoreDecodeException e ) 
          {
          }
     }
}

The next code sample adds a certificate to the key store and then verifies that the certificate exists. This code sample assumes that you have a WTLS certificate.

The first parameter for the keyStore.set method is an array containing the aliases needed to index this record in the key store. When you provide this information you are able to query the certificate with the alias that you specify, as shown in the previous example. However in this example the parameter is set to null, the certificate is put in the key store, and then keyStore.isMember() is called to check the membership. The isMember method creates an index for the certificate.

byte[] certData = { /* Certificate data as a byte array */ };
try 
    {
    //Assuming that you have a WTLS certificate, create a WTLS certificate.
    WTLSCertificate cert = WTLSCertificate( certData );
    keyStore.set(null, "my certificate", cert, new CertificateStatus(), null);
    ...
} 
catch ( CertificateParsingException e ) 
{
} 
catch ( NoSuchAlgorithmException e ) 
{
} 
catch ( InvalidKeyEncodingException e ) 
{
} 
catch ( InvalidKeyException e ) 
{
} 
catch ( CryptoTokenException e ) 
{
} 
catch ( CryptoUnsupportedOperationException e ) 
{
} 
catch ( KeyStorePasswordException e ) 
{
}

//Verify that the certificate is in the key store.
WTLSCertificate myNewCert = WTLSCertificate( certData );
if ( keyStore.isMember(myNewCert) ) 
     {
     System.out.println("Certificate provided is in the key store");
     }
else 
     {
     System.out.println("Certificate provided is not in the key store");
     }

Key agreement

Key agreement provides a way for two independent parties to establish a secure channel over an insecure network. Key agreement protocols generally use both parties' public and private keys, along with any algorithm-specific parameters, to create a secret piece of data. The secret data is created independently by both parties through the use of the publicly shared information and then is used as a session key for secure communication. The secret key is derived from public data but is not sent out over the network.

The RIM Cryptographic API supports the following key agreement schemes:

ECDHKeyAgreement is a common key agreement scheme. It requires the use of a public key and a private key. Each party in a secure communication uses their own private key and the public key of the other party. When each party uses the key agreement algorithm, the same secret data is created.

Code sample: Implementing ECDHKeyAgreement

The following code sample illustrates ECDHKeyAgreement. It assumes that sendPublicKey() and receivePublicKey() exist and enable the exchange of keys between the two clients. When a message is sent by one client, it is received by the other. Operations occur locally, and the details of the key exchange are not displayed. In your application, the public keys are exchanged over the network, and the private keys must be protected by each party. Identical shared secrets are created by each client (here called client1Secret and client2Secret).

This example generates the local secret on the local client. It creates a local shared secret that is identical to the local client's secret. It generates the secret key. It creates a local shared secret that is identical to the secret just generated.

//Generate the local secret on the local client.
public void generateLocalSecret() {
    // Create an EC crypto system for key creation
    ECCryptoSystem cryptoSystem = new ECCryptoSystem();

    // Create the first party's public and private keys
    ECKeyPair client1KeyPair = new ECKeyPair( cryptoSystem );

// Create a local shared secret that is identical to the local client's secret.
   // Send the local client public key (client1) to the remote user (client2).
   sendPublicKey( client1KeyPair.getECPublicKey() );
   ECPublicKey client2PublicKey = receivePublicKey();
   
   // Generate the shared secret for this client.
   byte[] client1Secret = ECDHKeyAgreement.generateSharedSecret(
                          client1KeyPair.getECPrivateKey(),
                          client2PublicKey );

   // Create a shared secret key based on the shared secret
   DESKey secretKey = new DESKey( client1Secret );
   }

// Generate the secret key, a shared key for communication between clients.
public void generateRemoteSecret() {
   // Create an EC crypto system for key creation
   ECCryptoSystem cryptoSystem = new ECCryptoSystem();

   // Create the remote public and private keys
   ECKeyPair client2KeyPair = new ECKeyPair( cryptoSystem );

// Create a local shared secret. It is identical to the secret just generated.
   // Send and receive the keys.
   sendPublicKey( client2KeyPair.getECPublicKey() );
   ECPublicKey client1PublicKey = receivePublicKey();

   // Generate the secret data based on the client1 public
   // key and client2 private key.
   byte[] client2Secret = ECDHKeyAgreement.generateSharedSecret(
            client2KeyPair.getECPrivateKey(),
            client1PublicKey );

   // Create a shared secret key based on this secret data.
   DESKey secretKey = new DESKey( client2Secret );
}

Code sample: Implementing ECMQVKeyAgreement

The following code sample illustrates ECMQVKeyAgreement. The code is similar to the ECDHKeyAgreement example, but this scheme makes use of an additional set of ephemeral (temporary) keys for added security. Ephemeral keys are keys that are stored and used for only a limited amount of time. This way, if a key is compromised, a new key is created periodically and the compromised key is no longer used. The example assumes that sendPublicKey() and receivePublicKey() are invoked and enable the exchange of keys between the two clients. If something is sent by one client, it is received by the other.

This example uses five keys: the local public ephemeral key, the local private ephemeral key, the local private static key, the remote public static key, and the remote public ephemeral key.

// Create a static (regular) key pair and an ephemeral key pair.
public void generateLocalSecret() {
   // Create an EC crypto system for key creation.
   ECCryptoSystem cryptoSystem = new ECCryptoSystem();
   // Create the first client's static and ephemeral key pairs.
   ECKeyPair client1StaticPair = new ECKeyPair( cryptoSystem );
   ECKeyPair client1EphemeralPair = new ECKeyPair( cryptoSystem );

// Send and receive the newly created public and public ephemeral key pair 
// using a predetermined key exchange protocol.
   // Send and receive public keys.
   sendPublicKey( client1StaticPair.getECPublicKey() );
   sendPublicKey( client1EphemeralPair.getECPublicKey() );
   ECPublicKey client2StaticKey = receivePublicKey();
   ECPublicKey client2EphemeralKey = receivePublicKey();
   // Generate the shared secret for this client. This
   // is identical to that generated by client2.
   byte[] client1Secret = ECMQVKeyAgreement.generateSharedSecret(
                 client1StaticPair.getECPrivateKey(),
                 client1EphemeralPair,
                 client2StaticKey,
                 client2EphemeralKey );
   // Create a shared secret key based on this secret data.
   DESKey secretKey = new DESKey( client1Secret );
   }

// Create a shared secret and use it to create a shared secret key.
public void generateRemoteSecret() {
   // Create an EC crypto system for key creation.
   ECCryptoSystem cryptoSystem = new ECCryptoSystem();

   // Create the second client's static and ephemeral key pairs.
   ECKeyPair client2StaticPair =
   new ECKeyPair( cryptoSystem );
   ECKeyPair client2EphemeralPair = new ECKeyPair( cryptoSystem );

   // Send and receive the keys.
   sendPublicKey( client2StaticPair.getECPublicKey() );
   sendPublicKey( client2EphemeralPair.getECPublicKey() );
   ECPublicKey client1StaticKey = receivePublicKey();
   ECPublicKey client1EphemeralKey = receivePublicKey();

   // Generate the shared secret for this client.
   byte[] client2Secret = ECMQVKeyAgreement.generateSharedSecret(
   client2StaticPair.getECPrivateKey(),
   client2EphemeralPair,
   client1StaticKey,
   client1EphemeralKey ); 

   // Create a shared secret key based on this secret data.
   DESKey secretKey = new DESKey( client2Secret );
}

Certificates

A certificate is an electronic document that is used to identify an individual, company, or other entity, and to associate that entity with a public key. A certificate uses a digital signature to bind the public key to an identity. When an entity wants to use a certificate, it can verify the signature to ensure that the key it provides is legitimate.

Certificate authorities are organizations that issue certificates and validate identities. Certificates can be self-signed, but they are usually signed by a certificate authority.

You can manage certificates with the net.rim.device.api.crypto.certificate package.

Each certificate on a BlackBerry smartphone has a certificate status. The certificate status indicates if the certificate is good, revoked, or unknown. Status values are managed by the net.rim.device.api.crypto.keystore.CertificateStatusManager class.

Code sample: Creating a certificate

If you have the encoding for a certificate, you can use code such as the following to create an X.509 certificate. Replace the text <encoding> with an appropriate value:

byte[] encoding = encoding;
X509Certificate certificate = new X509Certificate( <encoding> );

You can also use a certificate factory to create a certificate from its encoding. The certificate factory can read X.509 and WTLS certificates. For example:

InputStream encoding = encoding;
Certificate certificate = CertificateFactory.getInstance( "X509",encoding );

To create a new X.509 certificate, invoke X509Certificate.createCertificate(). The following code sample creates a root certificate (a certificate for which the issuer and subject are the same).

RSAKeyPair keyPair = new RSAKeyPair( new RSACryptoSystem() );
X509DistinguishedName name = new X509DistinguishedName(
    "O=Research In Motion,C=Canada" );
long keyUsage = KeyUsage.KEY_CERT_SIGN;
byte[] serialNumber = new byte[] { (byte)0x01 };
long validNotBefore = System.currentTimeMillis(); // Add a year of milliseconds.
long validNotAfter = validNotBefore + 1000*60*60*24*365;
X509Certificate root = X509Certificate.createX509Certificate( keyPair,
                          name, keyUsage, serialNumber, null,
                          validNotBefore, validNotAfter );

Certificate chains

The Certificate interface supports certificate chains. A certificate chain is a set of certificates that can provide greater security than a single certificate.

There are several ways to create a certificate chain.

If all the certificates are individual objects, then you can put them in an array.

Certificate endEntity = ...
Certificate firstCA = ...
Certificate rootCA = ...
Certificate[] chain = new Certificate[] { endEntity, firstCA, rootCA };

Alternatively, you can use the buildCertificateChain method, which is found in the CertificateUtilities class. The following example assumes that one of the certificate authorities is in an array of certificates, and that the other certificate authority is in a key store.

Certificate endEntity = ...
Certificate[] pool = ... ( contains firstCA )
KeyStore keyStore = ... ( contains rootCA )
Certificate[] chain = CertificateUtilities.buildCertChain( endEntity,
                      pool, keyStore );

You can use the following code to determine if the chain is trusted.

KeyStore trustedKeyStore = TrustedKeyStore.getInstance();
boolean trusted = CertificateUtilities.isCertificateChainTrusted( 
                  chain, trustedKeyStore )

Alternatively, you can attempt to create a trusted certificate chain directly. For example:

Certificate endEntity = ...
Certificate[] pool = ... ( contains firstCA )
KeyStore keyStore = ... ( contains rootCA )
KeyStore trustedKeyStore = TrustedKeyStore.getInstance();
Certificate[] trustedChain = CertificateUtilities.buildTrustedCertChain(
                             endEntity, pool,keyStore, trustedKeyStore );

Signatures

Signatures are typically used only with public/private key schemes. Signatures provide a way for verifying data origin authentication and identity authentication. If we can assume that an entity is the only one that knows the private key, then the private key can be used to prove to others that only that entity could have signed the information.

A signature can include one or more mathematical values, depending on the signing algorithm used, and so requires an encoding scheme.

The RIM Cryptographic API uses the net.rim.device.api.crypto.SignatureSigner interface for all signing functions. A SignatureSigner combines the message to be signed and the signer's private key to yield the mathematical values that make up a signature. A SignatureEncoder then encodes these mathematical values into a byte array for transmission or storage.

RSA signature signers

RSA signature signing uses the PKCS1SignatureSigner class, the PSSSignatureSigner class, or the X931SignatureSigner class. For RSA signatures, messages must be encoded by a formatter. Formatters are used in a similar way to the classes that implement the SignatureSigner interface.

Code sample

The following code sample creates the message to be signed. It creates the RSA key pair, the digest, and the salt value. (The salt value is random data that is appended to the key to make the key more difficult to break.) The code sample creates an RSASignatureSigner and passes in a digest algorithm and PSS signature formatter. The example could pass the signature signer into a stream, but instead it uses the signature signer directly. Finally, it encodes the signature using X.509.

// Create the message to be signed.
String message = "Hello World.";

// Create the necessary RSA key pair for signing and verifying.
RSACryptoSystem cryptoSystem = new RSACryptoSystem();
RSAKeyPair keyPair = new RSAKeyPair( cryptoSystem );

// Create the digest and the salt value.
SHA1Digest digest = new SHA1Digest();
byte[] salt = RandomSource.getBytes( digest.getDigestLength() );

// Create the RSASignatureSigner passing in a digest algorithm
// and PSS signature formatter.
PSSSignatureSigner signer =
         new PSSSignatureSigner( keyPair.getRSAPrivateKey(), digest, salt );

// For this example, simply use the signature signer directly,
// even though you could pass it into a stream as before.
signer.update( message.getBytes() );

// Encode the signature using X509.
EncodedSignature encSignature = SignatureEncoder.encode( signer, "X509" );

The following example shows how to decode and verify the signature (assuming the encoded signature and RSA key pair exist). It starts by decoding the encoded signature. It gives the verifier the message string. It checks that the signature verifies, and then prints it.

// Decode the encoded signature.
DecodedSignature decodedSignature = SignatureDecoder.decode(
encSignature.getEncodedSignature(), "X509" );
SignatureVerifier verifier = decodedSignature.getVerifier(
                                  keyPair.getPublicKey() );

// Give the verifier the message string.
verifier.update( message.getBytes() );

// Check that the signature verifies, and then print it.
if( verifier.verify() == true ) {
   System.out.println( "Signature verifies." );
}
else {
   System.out.println( "Signature does not verify." );
}

Implement signatures

The following procedure shows you how to use signatures with the DSA algorithm.

Create and use a signature signer, and create a key pair.

DSACryptoSystem cryptoSystem = new DSACryptoSystem();
   DSAKeyPair keyPair = new DSAKeyPair( cryptoSystem );
   DSAPrivateKey privateKey = keyPair.getDSAPrivateKey();

Specify the message to be signed.

String message = new String("Jeans are on sale");

Create the signer.

SignatureSigner signer = new DSASignatureSigner( privateKey );
   signer.update( message.getBytes() );

Create an X.509 signature.

EncodedSignature signature = SignatureEncoder.encode( signer, "X509" );

Get the data of the signature.

byte[] signatureData = signature.getEncodedSignature(); // bytes
   String encodingAlgorithm = signature.getEncodingAlgorithm(); // "X509"

Verify the signature.

  • Retrieve the public key.

    DSAPublicKey publicKey = keyPair.getDSAPublicKey();
    
  • Decode the signature.

    DecodedSignature decodedSignature = SignatureDecoder.decode(
       signatureData, "X509" );
  • Get the signature verifier.

    SignatureVerifier verifier = decodedSignature.getVerifier(publicKey);
  • Enter the message to be verified.

    verifier.update( message.getBytes() );
  • Verify the signature.

    boolean verified = verifier.verify();
  • Print out the result.

    System.out.println("Signature was verified " + verified + ".");

Signature streams

You can use the SignatureSigner class and SignatureVerifier class with a signature stream. The signature stream ( net.rim.device.api.crypto.SignatureSignerOutputStream ) allows you to write data to the stream. When you close the stream, the signature is appended to the stream.

Code sample

The following example shows how to use the signature stream. It generates a key pair, and then gets the private key. It creates the signature signer. It creates the output streams and then it writes out the data to be signed and signs it. It creates byte arrays for the signature. Finally, it closes the output streams and gets the stream contents.

// Generate a key pair that can be used for signing (and
// eventually for verifying).
ECKeyPair keyPair = new ECKeyPair( new ECCryptoSystem() );

// Get the private key for use with the signature signer.
ECPrivateKey privateKey = keyPair.getECPrivateKey();

// Create the signature signer. This example uses ECDSA.
ECDSASignatureSigner signer = new ECDSASignatureSigner( privateKey,
new MD5Digest() );

// Create the output stream for use as the underlying stream.
// This example uses a byte array output stream.
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

// Create the SignatureSignerOutputStream
SignatureSignerOutputStream signerStream = 
       new SignatureSignerOutputStream(signer, outputStream );

// Write out the data to be signed.
String info = new String("Hello World");

// Sign this information.
signerStream.write( info.getBytes() );

// Declare the byte arrays for r and s, which represent the signature.
byte[] r = new byte[signer.getRLength()];
byte[] s = new byte[signer.getSLength()];
signer.sign( r, 0, s, 0 );

// Close the output stream (upper layer).
signerStream.close();

// Get the stream contents for later.
byte[] contents = outputStream.toByteArray();

// Close the underlying output stream.
outputStream.close();

You can use the SignatureVerifierInputStream class to verify the signature that you sent to output in the previous section. The following code sample shows how to verify the signature that was generated using a signature output stream. It gets the public key from the last example. It sets up the signature verifier and the input stream. It reads the data from the stream and checks to see that the signature is correct. Finally, it prints the result.

// Get the public key from the key pair in the last example.
ECPublicKey publicKey = keyPair.getECPublicKey();

// Set up the ECDSASignatureVerifier.
ECDSASignatureVerifier verifier = new ECDSASignatureVerifier( publicKey,
                  new MD5Digest(), r, 0, s, 0 );

// Set up the input stream (using contents from above).
ByteArrayInputStream inputStream = new ByteArrayInputStream( contents );

// Use this to set up the SignatureVerifierInputStream
SignatureVerifierInputStream verifierStream =
                  new SignatureVerifierInputStream( verifier, inputStream );

// Read the data from the stream.
byte[] data = new byte[verifierStream.available()];
verifierStream.read( data );

// Use the verifier to check if the signature is correct.
boolean result = verifier.verify();

// Print out the result.
System.out.println("Signature verification was " + result + ".");

Digests

Digests are hash functions that are used to convert input data to a fixed-size hash. A digest can be used as a secure hash function to reduce a large amount of data into a small, unique identifier. The functionality for working with digests is provided in the net.rim.device.api.crypto.Digest interface.

The RIM Cryptographic API supports the following hash algorithms:

  • MD2 Digest
  • MD4 Digest
  • MD5 Digest
  • SHA-1 Digest
  • SHA-224 Digest, SHA-256 Digest, SHA-384 Digest, SHA-512 Digest
  • RIPEMD-128 Digest, RIPEMD-160 Digest

In North America, the most commonly used hash functions are SHA-1 and SHA-2. The digest length of SHA-1 is 160 bits. SHA-2 has variable digest bit lengths of 256 bits, 384 bits, and 512 bits. These lengths match the security level of the AES candidate. The RIM Cryptographic API supports SHA-1 (in the class SHA1Digest) and SHA-2 (in the classes SHA224Digest, SHA256Digest, SHA384Digest, and SHA512Digest).

Code sample: Using digests

The following code sample illustrates the use of SHA-1 and SHA-2, but all the algorithms are used in the same way. This example shows how to instantiate the different types of SHA. It uses the SHA-1 algorithm to show how the functions work. It updates the contents of the hash function. Finally, it gets the hash value.

// Instantiate any of the different types of SHA.
SHA1Digest digest160 = new SHA1Digest();
SHA256Digest digest256 = new SHA256Digest();
SHA384Digest digest384 = new SHA384Digest();
SHA512Digest digest512 = new SHA512Digest();

// Use the SHA-1 algorithm.
byte[] data = new byte[128];
RandomSource.getBytes( data );

// Update the contents of the hash function. (This is the data that gets hashed.)
digest160.update( data );
digest160.update( data, 10, 15 );

// Get the hash value.
byte[] digestValue = digest160.getDigest();

You can retrieve the hash value using the following code.

byte[] hashValue = new byte[digest160.getDigestLength()];
digest160.getDigest( hashValue, 0 );

The following code sample shows how to create an instance of a DigestOutputStream and then pass an instance of a digest algorithm to the constructor. You can use the same method for DigestInputStream, except that the data read from the input stream (passed to DigestInputStream) is passed through the digest algorithm.

This example creates a SHA digest of the default length. It then creates a new DigestOutputStream object. The example passes in a null value for the OutputStream parameter so that this stream does not pass the data written to it into another output stream. The example then invokes the write() method and finally gets the hash value.

// Create a SHA digest with the default length (160-bit).
SHA1Digest digest = new SHA1Digest();

// Create a new DigestOutputStream. 
DigestOutputStream out = new DigestOutputStream( digest, null );

// Invoke write to update the digest (assuming data exists and contains the message).
out.write( data );

// Get the hash value.
byte[] digestValue = digest.getDigest();

MACs

A MAC is used to verify that the information passed through the MAC has not been changed or modified. It is essentially a keyed hash function. A key is required for the creation and verification of the hash value.

MAC functionality is based on the Digest interface and is provided in the net.rim.device.api.crypto.MAC interface. A MAC can be used with a digest to ensure that data is not modified without permission.

The RIM Cryptographic API supports the following MACs:

  • CBC MAC
  • HMAC
  • Null MAC

The HMAC class implements the MAC interface and allows for easy use of keyed hash functions. The code required to use an HMAC is similar to that for digests, except that you require a key to generate the hash.

Code sample

The following example creates an HMAC key, which can be an array of data of any length. In most cases, a length equal to the bit size of the digest is recommended. Random data is used in this example.

byte[] keyData = new byte[ 20 ];
RandomSource.getBytes( keyData );

// Create the key.
HMACKey key = new HMACKey( keyData );

// Create the SHA digest.
SHA1Digest digest = new SHA1Digest();

// Create the HMAC, passing in the key and the SHA digest. 
// Any instance of a digest can be used here.
HMAC hMac = new HMAC( key, digest );

// The HMAC can be updated much like a digest.
hMac.update( data );
hMac.update( data, 10, 15 );

// Now get the MAC value.
byte[] macValue = hMac.getMAC();