• Aucun résultat trouvé

Algorithm Names

Dans le document Android Security Internals (Page 147-155)

The string algorithm parameter that all factory methods take maps to a particular cryptographic algorithm or transformation, or specifies an implementation strategy for higher-level objects that manage collections of certificates or keys. Usually, the mapping is straightforward. For example, SHA-256 maps to an implementation of the SHA-256 hashing algorithm and AES requests an implementation of the AES encryption algorithm.

However, some algorithm names have structure and specify more than one parameter of the requested implementation. For example, SHA256withRSA specifies a signature implementation that uses SHA-256 for hashing the signed message and RSA to perform the signature operation. Algorithms can also have aliases, and more than one algorithm name can map to the same implementation.

Algorithm names are case-insensitive. The standard algorithm names supported by each JCA engine class are defined in the JCA Standard Algorithm Name Documentation (sometimes referred to as just Standard Names).1 In addi-tion to those, providers can define their own algorithm names and aliases.

(See each provider’s documentation for details.) You can use the code in Listing 5-6 to list all providers, the algorithm names of cryptographic ser-vices offered by each provider, and the implementation classes they map to.

Provider[] providers = Security.getProviders();

for (Provider p : providers) {

System.out.printf("%s/%s/%f\n", p.getName(), p.getInfo(), p.getVersion());

Set<Service> services = p.getServices();

for (Service s : services) {

System.out.printf("\t%s/%s/%s\n", s.getType(), s.getAlgorithm(), s.getClassName());

} }

Listing 5-6: Listing all JCA providers and the algorithms they support

We will show the format for the algorithm name of major engine classes as we introduce them in the following sections.

SecureRandom

The SecureRandom class represents a cryptographic Random Number Generator (RNG). While you may not directly use it too often, it is used internally by most cryptographic operations to generate keys and other cryptographic material. The typical software implementation is usually a Cryptographically Secure Pseudo Random Number Generator (CSPRNG), which produces a sequence of numbers that approximate the properties of true random numbers based on an initial value called a seed. As the quality of random numbers produced

1. Oracle, Java™ Cryptography Architecture Standard Algorithm Name Documentation, http://docs .oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html

by a CSPRNG largely depends on its seed, it is chosen carefully, usually based on the output of a true RNG.

On Android, CSPRNG implementations are seeded by reading seed bytes from the standard Linux /dev/urandom device file, which is an inter-face to the kernel CSPRNG. As the kernel CSPRNG itself may be in a fairly predictable state right after starting, Android periodically saves the state (which is 4096 bytes as of Android 4.4) of the kernel CSPRNG to the /data/system/entropy.dat file. The contents of that file are written back to /dev/urandom on boot in order to carry over the previous CSPRNG state. This is performed by the EntropyMixer system service.

Unlike most engine classes, SecureRandom has public constructors that you can use to create an instance. The recommended way to get a properly seeded instance on Android is to use the default (no argument) constructor (u in Listing 5-7). If you use the getInstance() factory method, you need to pass SHA1PRNG as the algorithm name, which is the only universally sup-ported algorithm name for SecureRandom. Because SHA1PRNG is not exactly a cryptographic standard, implementations from different providers might behave differently. To have SecureRandom generate random bytes, you pass a byte array to its nextBytes() method (v in Listing 5-7). It will generate as many bytes as the array length (16 in Listing 5-7) and store them in it.

SecureRandom sr = new SecureRandom();u byte[] output = new byte[16];

sr.nextBytes(output);v

Listing 5-7: Using SecureRandom to generate random bytes

Seeding SecureRandom manually is not recommended because seeding the system CSPRNG improperly may result in it producing a predictable sequence of bytes, which could compromise any higher-level operations that require random input. However, if you need to manually seed SecureRandom

for some reason (for example, if the default system seeding implementation is known to be flawed), you can do so by using the SecureRandom(byte[] seed)

constructor or by calling the setSeed() method. When seeding manually, make sure that the seed you are using is sufficiently random; for example, by reading it from /dev/urandom.

Additionally, depending on the underlying implementation, calling

setSeed() may not replace, but instead only add to the internal CSPRNG state; so two SecureRandom instances seeded with the same seed value may not produce the same number sequence. Therefore, SecureRandom should not be used when deterministic values are required. Instead, use a cryptographic primitive that is designed to produce deterministic output from a given input, such as a hash algorithm or a key derivation function.

MessageDigest

The MessageDigest class represents the functionality of a cryptographic mes-sage digest, also referred to as a hash function. A cryptographic mesmes-sage digest takes an arbitrarily long sequence of bytes and generates a fixed-size

byte sequence called a digest or hash. A good hash function guarantees that even a small change in its input results in completely different output and that it is very difficult to find two inputs that are different but produce the same hash value (collision resistance), or generate an input that has a given hash (pre-image resistance). Another important property of hash functions is second pre-image resistance. In order to withstand second pre-image attacks, a hash function should make it difficult to find a second input m2 that hashes to the same value as a given input m1.

Listing 5-8 shows how to use the MessageDigest class.

MessageDigest md = MessageDigest.getInstance("SHA-256");u byte[] data = getMessage();

byte[] digest = md.digest(data);v Listing 5-8: Using MessageDigest to hash data

A MessageDigest instance is created by passing the hash algorithm name to the getInstance() factory method u. Input may be provided in chunks by using one of the update() methods, and then calling one of the digest()

methods to get the calculated hash value. Alternatively, if the input data size is fixed and relatively short, it can be hashed in one step by using the

digest(byte[] input) method v, as shown in Listing 5-8.

Signature

The Signature class provides a common interface for digital signature algo-rithms based on asymmetric encryption. A digital signature algorithm takes an arbitrary message and a private key and produces a fixed-sized byte string called a signature. Digital signatures typically apply a digest algorithm to the input message, encode the calculated hash value, and then use a private key operation to produce the signature. The signature can then be verified using the corresponding public key by applying the reverse operation, cal-culating the hash value of the signed message, and comparing it to the one encoded in the signature. Successful verification guarantees the integrity of the signed message and, on the condition that the signing private key has remained indeed private, its authenticity.

Signature instances are created with the standard getInstance() factory method. The algorithm name used is generally in the form <digest>with

<encryption>, where <digest> is a hash algorithm name as used by MessageDigest

(such as SHA256), and <encryption> is an asymmetric encryption algorithm (such as RSA or DSA). For example, a SHA512withRSA Signature would first use the SHA-512 hash algorithm to produce a digest value and then encrypt the encoded digest with an RSA private key to produce the signature. For signature algorithms that use a mask generation function such as RSA-PSS, the algorithm name takes the form <digest>with<encryption>and<mgf> (for example, SHA256withRSAandMGF1).

Listing 5-9 shows how to use the Signature class to generate and verify a cryptographic signature.

PrivateKey privKey = getPrivateKey();

PublicKey pubKey = getPublicKey();

byte[] data = "sign me".getBytes("ASCII");

Signature sig = Signature.getInstance("SHA256withRSA");

sig.initSign(privKey);u sig.update(data);v

byte[] signature = sig.sign();w sig.initVerify(pubKey);x sig.update(data);

boolean valid = sig.verify(signature);y

Listing 5-9: Generating and verifying a signature with the Signature class

After obtaining an instance, the Signature object is initialized for either signing, by passing a private key to the initSign() method (u in Listing 5-9), or verification, by passing a public key or certificate to the initVerify()

method x for verification.

Signing is similar to calculating a hash with MessageDigest: the data to be signed is fed in chunks to one of the update() methods v or in bulk to the

sign() method w, which returns the signature value. To verify a signature, the signed data is passed to one of the update() methods. Finally, the signa-ture is passed to the verify() method y, which returns true if the signature is valid.

Cipher

The Cipher class provides a common interface to encryption and decryption operations. Encryption is the process of using some algorithm (called a cipher) and a key to transform data (called plaintext, or plaintext message) into a randomly looking form (called ciphertext). The inverse operation, called decryption, transforms the ciphertext back into the original plaintext.

The two major types of encryption widely used today are symmetric encryption and asymmetric encryption. Symmetric, or secret key, encryption uses the same key to encrypt and decrypt data. Asymmetric encryption uses a pair of keys: a public key and a private key. Data encrypted with one of the keys can only be decrypted with the other key of the pair. The Cipher class supports both symmetric and asymmetric encryption.

Depending on how they process input, ciphers can be block or stream.

Block ciphers work on fixed-sized chunks of data called blocks. If the input cannot be divided into an integral number of blocks, the last block is pad-ded by adding the necessary number of bytes to match the block size. Both the operation and the added bytes are called padding. Padding is removed in the decryption process and is not included in the decrypted plaintext. If a padding algorithm is specified, the Cipher class can add and remove pad-ding automatically. On the other hand, stream ciphers process input data one byte (or even bit) at a time and do not require padding.

Block Cipher Modes of Operation

Block ciphers employ different strategies when processing input blocks in order to produce the final ciphertext (or plaintext when decrypting). Those strategies are called modes of operation, cipher modes, or simply modes. The sim-plest processing strategy is to split the plaintext into blocks (padding as nec-essary), apply the cipher to each block, and then concatenate the encrypted blocks to produce the ciphertext. This mode is called Electronic Code Book (ECB) mode, and while it’s straightforward and easy to use, it has the major disadvantage that identical plaintext blocks produce identical ciphertext blocks. Thus, plaintext structure is reflected in the ciphertext, which com-promises message confidentiality and facilitates cryptanalysis. This has often been illustrated with the infamous “ECB Penguin” from the Wikipedia entry on block cipher modes.2 We present our Android version in Figure 5-2.3 Here, u is the original image, v is the image encrypted in ECB mode, and w is the same image encrypted in CBC mode. As you can see, the pattern of the origi-nal image is distinguishable in v, while w looks like random noise.

Figure 5-2: Ciphertext patterns produced by different cipher modes

Feedback modes add randomness to the ciphertext by combining the pre-vious encrypted block with the current plaintext block before encrypting.

In order to produce the first cipher block, they combine the first plaintext block with a block-sized string of bytes not found in the original plain text, called an initialization vector (IV). When configured to use a feedback mode, the Cipher class can use a client-specified IV or generate one automatically.

Commonly used feedback modes are Cipher-block chaining (CBC), Cipher feed-back (CFB), and Output feedfeed-back (OFB).

Another way to add randomness to the ciphertext, employed by the Counter (CTR) mode, is to encrypt the successive values of a counter sequence in order to produce a new key for each plaintext block that needs to be encrypted. This effectively turns the underlying block cipher into a stream cipher and no padding is required.

2. Wikipedia, “Block cipher mode of operation,” https://en.wikipedia.org/wiki/

Block_cipher_mode_of_operation

3. The Android robot is reproduced or modified from work created and shared by Google and used according to terms described in the Creative Commons 3.0 Attribution License.

u v w

Newer cipher modes, such as Galois/Counter Mode (GCM), not only dif-fuse patterns in the original plaintext but also authenticate the ciphertext, making sure it has not been tampered with. They provide authenticated encryption (AE) or Authenticated Encryption with Associated Data(AEAD).4 The

Cipher APIs have been extended to support authenticated encryption in Java SE 7, and those extensions have been available since Android 4.4, which has a Java 7–compatible runtime library API. AE ciphers concatenate the authentication tag output by the encryption operation to the ciphertext that operation produces in order to form their final output. In the Java

Cipher API, the tag is included (or verified, when decrypting) implicitly after calling doFinal(), so you should not use the output of update() until you’re sure the implicit tag at the end validates.

Obtaining a Cipher Instance

Having reviewed the major parameters of a cipher, we can finally discuss how to create Cipher instances. Like the other engine classes, Cipher objects are created with the getInstance() factory method, which requires not just a simple algorithm name, but that you fully specify the cryptographic transfor-mation that the requested cipher will perform.

Listing 5-10 shows how to create a Cipher instance by passing a transfor-mation string to getInstance().

Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");

Listing 5-10: Creating a Cipher instance

A transformation needs to specify the encryption algorithm, cipher mode, and padding. The transformation string passed to getInstance() is in the algorithm/mode/padding format. For example, the transformation string used in Listing 5-10 would create a Cipher instance that uses AES as the encryption algorithm, CBC as the cipher mode, and PKCS#5 padding.

N O T E The term PKCS will appear quite a few times in our discussion of JCA providers and engine classes. The acronym stands for Public Key Cryptography Standard and refers to a group of cryptography standards that were originally developed and published by RSA Security, Inc. in the early 1990s. Most have evolved into public Internet standard and are now published and maintained as RFCs (Requests for Comments, formal documents describing Internet standards), but they are still referred to by their original name. Notable standards include PKCS#1, which defines the basic algorithms for RSA encryption and signatures; PKCS#5, which defines password-based encryption; PKCS#7, which defines message encryption and signing under a PKI and became the basis of S/MIME; and PKCS#12, which defines a container for keys and certificates. A full list can be found on EMC’s website.5

4. D. McGrew, RFC 5116 – An Interface and Algorithms for Authenticated Encryption, http://www.ietf .org/rfc/rfc5116.txt

5. RSA Laboratories, Public-Key Cryptography Standards (PKCS), http://www.emc.com/emc-plus/

A Cipher instance can be created by passing only the algorithm name, but in that case the returned implementation would use provider-specific defaults for the cipher mode and padding. This is not only not portable across providers, but could severely impact the security of the system if, for example, a less-secure-than-intended cipher mode (such as ECB) is used at runtime. This “shortcut” is a major design flaw of the JCA provider frame-work and should never be used.

Using a Cipher

Once a Cipher instance has been obtained, it needs to be initialized before encrypting or decrypting data. A Cipher is initialized by passing an inte-ger constant that denotes the operation mode (ENCRYPT_MODE, DECRYPT_MODE,

WRAP_MODE, or UNWRAP_MODE), a key or certificate, and, optionally, algorithm parameters, to one of the corresponding init() methods. ENCRYPT_MODE and

DECRYPT_MODE are used to encrypt and decrypt arbitrary data, while WRAP_MODE

and UNWRAP_MODE are specialized modes used when encrypting (wrapping) and decrypting (unwrapping) the key material of a Key object with another key.

Listing 5-11 shows how to use the Cipher class to encrypt and decrypt data.

SecureRandom sr = new SecureRandom();

SecretKey key = getSecretKey();

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");u byte[] iv = new byte[cipher.getBlockSize()];

sr.nextBytes(iv);

IvParameterSpec ivParams = new IvParameterSpec(iv);v cipher.init(Cipher.ENCRYPT_MODE, key, ivParams);w byte[] plaintext = "encrypt me".getBytes("UTF-8");

ByteArrayOutputStream baos = new ByteArrayOutputStream();

byte[] output = cipher.update(plaintext);x if (output != null) {

baos.write(output);

}

output = cipher.doFinal();y baos.write(output);

byte[] ciphertext = baos.toByteArray();

cipher.init(Cipher.DECRYPT_MODE, key, ivParams);z baos = new ByteArrayOutputStream();

output = cipher.update(ciphertext);{

if (output != null) { baos.write(output);

}

output = cipher.doFinal();|

baos.write(output);

byte[] decryptedPlaintext = baos.toByteArray();}

Listing 5-11: Using the Cipher class to encrypt and decrypt data

In this example, we create a Cipher instance that uses AES in CBC mode and PKCS#5 padding u; generate a random IV and wrap it into an

IvParameterSpec object v; and then initialize the Cipher for encryption by passing ENCRYPT_MODE, the encryption key, and the IV to the init() method w.

We can then encrypt data by passing data chunks to the update() method x, which returns intermediate results (or null if the input data is too short to result in a new block), and obtain the last block by calling the doFinal()

method y. The final ciphertext is obtained by concatenating the intermedi-ate result(s) with the final block.

To decrypt, we initialize the cipher in DECRYPT_MODE z, passing the same key and the IV used for encryption. We then call update() {, this time using the ciphertext as input, and finally call doFinal() | to obtain the last chunk of plaintext. The final plaintext is obtained by concatenating the intermedi-ate result(s) with the final chunk }.

Mac

The Mac class provides a common interface to Message Authentication Code (MAC) algorithms. A MAC is used to check the integrity of messages trans-mitted over an unreliable channel. MAC algorithms use a secret key to calculate a value, the MAC (also called a tag), which can be used to authen-ticate the message and check its integrity. The same key is used to perform verification, so it needs to be shared between the communicating parties.

(A MAC is often combined with a cipher to provide both confidentiality and integrity.)

KeyGenerator keygen = KeyGenerator.getInstance("HmacSha256");

SecretKey key = keygen.generateKey();

Mac mac = Mac.getInstance("HmacSha256");u mac.init(key);v

byte[] message = "MAC me".getBytes("UTF-8");

byte[] tag = mac.doFinal(message);w

Listing 5-12: Using the Mac class to generate a message authentication code

A Mac instance is obtained with the getInstance() factory method u (as shown in Listing 5-12) by requesting an implementation of the HMAC6 MAC algorithm that uses SHA-256 as the hash function. It is then initial-ized v with a SecretKey instance, which may be generated with a KeyGenerator

(see “KeyGenerator” on page 131), derived from a password or directly instantiated from raw key bytes. For MAC implementations based on hash functions (such as HMAC SHA-256 in this example), the type of key does not matter, but implementations that use a symmetric cipher may require a matching key type to be passed. We can then pass the message in chunks using one of the update() methods and call doFinal() to obtain the final MAC value, or perform the operation in one step by passing the message bytes directly to doFinal() w.

6. H. Krawczyk, M. Bellare, and R. Canetti, HMAC: Keyed-Hashing for Message Authentication,

Key

The Key interface represents opaque keys in the JCA framework. Opaque keys can be used in cryptographic operations, but usually do not provide access to the underlying key material (raw key bytes). This allows us to use the same JCA classes and interfaces both with software implementations of cryptographic algorithms that store key material on memory, and with hardware-backed ones, where the key material may reside in a hardware token (smart card, HSM,7 and so on) and is not directly accessible.

The Key interface defines only three methods:

String getAlgorithm() Returns the name of the encryption algorithm (symmetric or asymmetric) that this key can be used with. Examples are AES or RSA.

byte[] getEncoded() Returns a standard encoded form of the key that can be used when transmitting the key to other systems. This can be encrypted for private keys. For hardware-backed implementations that

byte[] getEncoded() Returns a standard encoded form of the key that can be used when transmitting the key to other systems. This can be encrypted for private keys. For hardware-backed implementations that

Dans le document Android Security Internals (Page 147-155)