Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Professional Java.JDK.5.Edition (Wrox)

.pdf
Скачиваний:
31
Добавлен:
29.02.2016
Размер:
12.07 Mб
Скачать

Chapter 13

System.out.println(“Exception: “ + e);

}

}

public void write(byte[] bytes, OutputStream out)

{

try {

CipherOutputStream cos =

new CipherOutputStream(out, m_encrypter);

cos.write(bytes, 0, bytes.length);

cos.close();

} catch(IOException ioe) { System.out.println(“Exception: “ + ioe);

}

}

public void read(byte[] bytes, InputStream in)

{

try {

CipherInputStream cis =

new CipherInputStream(in, m_decrypter);

int pos=0, intValue;

while( (intValue = cis.read()) != -1) { bytes[pos] = (byte)intValue; pos++;

}

} catch(IOException ioe) { System.out.println(“Exception: “ + ioe);

}

}

public byte[] encrypt(byte[] input)

{

try { return(m_encrypter.doFinal(input));

}catch(IllegalBlockSizeException ibse) { System.out.println(“Exception: “ + ibse);

}catch(BadPaddingException bpe) { System.out.println(“Exception: “ + bpe);

}

return(null);

}

public byte[] decrypt(byte[] input)

{

try { return(m_decrypter.doFinal(input));

}catch(IllegalBlockSizeException ibse) { System.out.println(“Exception: “ + ibse);

}catch(BadPaddingException bpe) {

606

Java Security

System.out.println(“Exception: “ + bpe);

}

return(null);

}

public static void main(String args[])

{

try {

CipherExample ce = new CipherExample();

SecretKey key =

KeyGenerator.getInstance(“DES”).generateKey();

ce.init(key);

System.out.println(“Testing encrypt/decrypt of bytes”); byte[] clearText = new byte[]{65,73,82,68,65,78,67,69}; byte[] encryptedText = ce.encrypt(clearText);

byte[] decryptedText = ce.decrypt(encryptedText);

String clearTextAsString = new String(clearText);

String encTextAsString = new String(encryptedText);

String decTextAsString = new String(decryptedText);

System.out.println(“

CLEARTEXT: “ + clearTextAsString);

System.out.println(“

ENCRYPTED: “ + encTextAsString);

System.out.println(“

DECRYPTED: “ + decTextAsString);

System.out.println(“\nTesting encrypting of a file\n”);

FileInputStream fis = new FileInputStream(“cipherTest.in”); FileOutputStream fos =

new FileOutputStream(“cipherTest.out”);

int dataInputSize = fis.available();

byte[] inputBytes = new byte[dataInputSize]; fis.read(inputBytes);

ce.write(inputBytes, fos); fos.flush();

fis.close();

fos.close();

String inputFileAsString = new String(inputBytes); System.out.println(“INPUT FILE CONTENTS\n” +

inputFileAsString + “\n”);

System.out.println(“File encrypted and saved to disk\n”);

fis = new FileInputStream(“cipherTest.out”);

byte[] decrypted = new byte[dataInputSize]; ce.read(decrypted, fis);

607

Chapter 13

fis.close();

String decryptedAsString = new String(decrypted);

System.out.println(“DECRYPTED FILE:\n” +

decryptedAsString + “\n”);

}catch(IOException ioe) { System.out.println(“Exception: “ + ioe);

}catch(NoSuchAlgorithmException e) { System.out.println(“Exception: “ + e);

}

}

}

The KeyGenerator engine class is used to generate a SecretKey. This class accepts a SecretKey as an initialization parameter, which is then used by the various instances of the Cipher class.

KeyGenerator

This engine class is used to generate secret keys for symmetric algorithms. The standard getInstance methods are available. After object creation, one of the following methods is used to initialize the KeyGenerator.

The following are the algorithm-independent initialization methods:

public void init(SecureRandom random) public void init(int keysize)

public void init(int keysize, SecureRandom random)

The following are the algorithm-specific initialization methods:

public void init(AlgorithmParameterSpec params)

public void init(AlgorithmParameterSpec params, SecureRandom random)

The algorithm-independent initialization allows you to specify a RNG or a keysize — or both — as parameters used to initialize the key generator. The algorithm-specific initialization methods accept a set of parameters (and possibly a RNG also) that are used with the chosen algorithm. What these parameters are depend on the algorithm used:

public SecretKey generateKey()

After the KeyGenerator is created and initialized, the generateKey method is called to generate a secret key. Consult the previous Cipher example for a use of the KeyGenerator in action.

SecretKeyFactory

This is very much like the KeyFactory in the java.security package; however, this engine class only works on secret (symmetric) keys. This class is used to convert keys back and forth between their transparent and opaque representations. The standard getInstance methods are used to create a SecretKeyFactory object. Three main methods are used to manipulate keys: generateSecret, getKeySpec, and translateKey:

SecretKey generateSecret(KeySpec keySpec)

608

Java Security

This converts a key specification into a SecretKey object. If the factory cannot convert the key using the current algorithm, an InvalidKeySpecException is thrown:

KeySpec getKeySpec(SecretKey key, Class keySpec)

This converts a key into a key specification in the format specified by the keySpec parameter. If the factory cannot perform the conversion due to the algorithm or some other mismatch (such as incompatible formats), an InvalidKeySpecException is thrown:

SecretKey translateKey(SecretKey key)

This translates a key object from an unknown or untrusted provider to a key object from this factory.

Protecting Objects through Sealing

The SealedObject class is used to encrypt any class that is serializable. It is used with an instance of the Cipher class. The constructor of SealedObject is used to specify an object to seal an initialized Cipher object. One of three getObject methods is later used to decrypt the object. The name of the algorithm used to encrypt the object can be retrieved using the getAlgorithm method:

Object getObject(Cipher c)

Object getObject(Key key)

Object getObject(Key key, String provider)

The first form decrypts the object using a provided Cipher. The second decrypts the object using the algorithm that encrypted the object and requires a key for decryption. The final form allows you to specify a specific provider along with the key needed to decrypt the object.

Here’s an example of creating a custom class, sealing it, and then unsealing it:

import java.security.*; import java.security.spec.*; import javax.crypto.*; import javax.crypto.spec.*; import java.io.*;

class CustomerData implements Serializable { public String name;

public String password;

}

public class SealedObjectExample {

private SecretKey secretKey; private Cipher encrypter, decrypter;

public SealedObjectExample()

{

try {

secretKey = KeyGenerator.getInstance(“DES”).generateKey();

encrypter = Cipher.getInstance(“DES”); encrypter.init(Cipher.ENCRYPT_MODE, secretKey);

decrypter = Cipher.getInstance(“DES”);

609

Chapter 13

decrypter.init(Cipher.DECRYPT_MODE, secretKey);

}catch(NoSuchAlgorithmException e) {

}catch(InvalidKeyException e) {

}catch(NoSuchPaddingException e) {

}

public SealedObject seal(Serializable obj)

{

try {

return(new SealedObject(obj, encrypter));

}catch(IOException e) {

}catch(IllegalBlockSizeException e) {

return(null);

}

public Object unseal(SealedObject so)

{

try {

String algorithmName = so.getAlgorithm();

// can use algorithmName to construct a decrypter

return(so.getObject(decrypter));

}catch(IOException e) {

}catch(IllegalBlockSizeException e) {

}catch(BadPaddingException e) {

}catch(ClassNotFoundException e) {

}

return(null);

}

public static void main(String args[])

{

CustomerData cust, unsealed; SealedObject sealed;

SealedObjectExample soe = new SealedObjectExample();

//configure a CustomerData object cust = new CustomerData(); cust.name = “Paul”;

cust.password = “password”;

//Seal it, storing it in a SealedObject sealed = soe.seal(cust);

//Try unsealing it

unsealed = (CustomerData)soe.unseal(sealed);

System.out.println(“NAME: “ + unsealed.name); System.out.println(“PASSWORD: “ + unsealed.password);

}

}

610

Java Security

The only requirement on the class that will be sealed is that it inherits from serializable. The SealedObject class contains the sealed object, and to unseal the object, all that is necessary is the SealedObject object. It is possible to retrieve the name of the algorithm used to seal it using the getAlgorithm method on the SealedObject class. A Cipher object is also needed to perform the unsealing operation.

Computing Message Authentication Codes

The Mac engine class computes a hash, similar to a message digest, for input data given a secret key. The Mac class has the standard getInstance methods for object creation. After creation, the object must be initialized using one of the following methods:

public void init(Key key)

public void init(Key key, AlgorithmParameterSpec params)

The key must be a key class that inherits from the javax.crypto.SecretKey interface, such as KeyGenerator.generateKey() or KeyAgreement.generateSecret(). Certain algorithms require that the algorithm used to generate the key be compatible with the algorithm specified in the getInstance call. If this is the case and the two algorithms are not compatible, an InvalidKeyException is thrown.

The Mac class follows similar semantics for sending data to the computation engine. Data can be passed in all at once using the doFinal method or passed in piece by piece using the update method (and then invoking doFinal to signal the end of the input):

public byte[] doFinal(byte[] input) public byte[] doFinal()

public void doFinal(byte[] output, int outOffset)

The first doFinal method accepts a byte array containing the input data, computes the message authentication code, and returns that in a byte array. The second form is used to signal the end of input after several invocations of the update method. The last form can be used after a sequence of update methods to both accept the last chunk of data and signal the end of input. The outOffset parameter specifies where in the output array to start reading data, and the data ends at the end of the array:

public void update(byte input) public void update(byte[] input)

public void update(byte[] input, int inputOffset, int inputLen)

These methods are useful for sending data to the Mac class a piece at a time. The first method accepts a single byte of input. The second method takes an array of bytes. The third allows you to pass in an array and specify where in the array (starting at inputOffset) to read the data, and the length of the data (inputLen). After you are done calling update, don’t forget to call a version of doFinal to signal the end of input and calculate the message authentication code.

Here’s an example of creating an instance of the Mac class and computing the message authentication code for data. This example leverages the previous KeyGeneratorExample:

import java.security.*; import javax.crypto.*; import java.io.*;

public class MacExample {

611

Chapter 13

public static void main(String args[])

{

try {

String inputString = “Test input string”;

KeyGenerator keyGen = KeyGenerator.getInstance(“HmacMD5”);

SecretKey secretKey = keyGen.generateKey();

Mac mac = Mac.getInstance(secretKey.getAlgorithm()); mac.init(secretKey);

//the Mac class needs data in byte format byte[] byteData = inputString.getBytes(“UTF8”);

//Compute the MAC for the data all in one operation byte[] macBytes = mac.doFinal(byteData);

String macAsString =

new sun.misc.BASE64Encoder().encode(macBytes);

System.out.println(

“The computed message authentication code is: “

+macAsString);

}catch (InvalidKeyException e) {

}catch (NoSuchAlgorithmException e) {

}catch (UnsupportedEncodingException e) {

}

}

Computing the message authentication code is very similar to computing the message digest using the MessageDigest engine class. The main difference is that a key is required. Here, the key is generated via the KeyGenerator. Normally, this key would be saved or transmitted for verifying that the MAC matches the data. The Mac class is created with the same algorithm used for the key and then initialized with the key.

Program Security Using JAAS

JAAS stands for Java Authentication and Authorization Service. This package used to be an extension, but it was made part of the J2SDK in the 1.4 release of the JDK. Authentication is the process by which the user of the application (any type of Java program, including applets, servlets, and so forth) is verified. Authorization is the process by which an authenticated user is granted permission for executing actions, such as modifying specific files that are access-controlled. Authentication and authorization work together to provide access control for your program, but these are separate concepts.

User Identification

In order for access control to work, there must be a way of storing the user’s identity. This is accomplished using a Subject, a grouping of information that identifies the source of all requests, such as a particular user that is logged in to the system. A Subject has associated principals, which are other identifying characteristics of a subject, such as a user’s social security number or name. A Subject also has public and private credentials, such as a public and private key, but it can be any object.

612

Java Security

You won’t usually need to instantiate a Subject; however, there are two constructors provided:

public Subject();

public Subject(boolean readOnly, Set principals,

Set pubCredentials, Set privCredentials);

The first constructor creates a Subject that isn’t read-only and has empty (not null) sets of principals, and public and private credentials. The second constructor gives you an idea of the information the Subject possesses:

public void setReadOnly();

public boolean isReadOnly();

These two methods allow you to change and retrieve the read-only state of the subject. If the Subject is marked read-only and an attempt is made to change the principals or credentials, an IllegalStateException is thrown:

public Set getPrincipals(); public Set getPrincipals(Class c); public Set getPublicCredentials();

public Set getPublicCredentials(Class c); public Set getPrivateCredentials(); public Set getPrivateCredentials(Class c);

These methods allow you to retrieve a handle to the set of principals, or public or private credentials. Once you have this handle, you can use the methods on the Set class to manipulate the contents of the set. Modifying this set modifies the set in the Subject.

Each version of this access method has a Class parameter that allows you to retrieve only those principals/credentials that are of a specific type. However, these methods return a new set that does not correspond to the internal set of the Subject.

A Subject can be associated with an AccessControlContext. This is a snapshot of context from an AccessController that governs how security checks are performed. You can access this through the following method:

public static Subject getSubject(final AccessControlContext acc);

Note that this is a static method. This method returns the Subject currently associated with the specified AccessControlContext or null if there is no association.

Executing Code with Security Checks

The Subject class provides doAs and doAsPrivileged methods to execute code that contains security restrictions. The java.security.PrivilegedAction interface must be implemented by another class in order to package code for use with the doAs or doAsPrivleged methods. Only one method is defined in this interface:

public Object run();

613

Chapter 13

Any code in the run method executes with the Subject passed to the doAs or doAsPrivileged method. If permission for all the operations in the code is not granted to the Subject/principals, then a SecurityException is thrown. The value returned can have any meaning you wish to associate with it or simply return null if you don’t need to pass any information back.

The doAs method executes a specified block of code as a particular Subject:

public static Object doAs(final Subject subject,

final java.security.PrivilegedAction action);

public static Object doAs(final Subject subject,

final java.security.PrivilegedExceptionAction action) throws java.security.PrivilegedActionException;

These methods first associate the Subject with the current thread’s AccessControlContext and then execute the action. The first form expects the method to return, and the second allows checked exceptions to be thrown from the executing code.

The doAsPrivileged method operates the same as the doAs method, but it allows you to specify which AccessControlContext to use instead of the one attached to the current thread:

public static Object doAsPrivileged(final Subject subject,

final java.security.PrivilegedAction action, final java.security.AccessControlContext acc);

public static Object doAsPrivileged(final Subject subject,

final java.security.PrivilegedExceptionAction action, final java.security.AccessControlContext acc)

throws java.security.PrivilegedActionException;

These provide a third parameter to both methods for using a different AccessControlContext.

Principals

A principal can be of any class type as long as the class inherits from java.security.Principal and java.io.Serializable. The Principal interface defines the following methods:

boolean equals(Object another)

The equals method returns true if the principal passed in matches the current principal and returns false otherwise:

String toString()

The toString method returns a string representation of this principal:

int hashCode()

The hashCode method returns a hash code for this principal:

String getName()

The getName method returns the name of this principal.

614

Java Security

Credentials

Credentials can be of any type, and no requirements are placed on what interfaces a credential class must implement. However, JAAS provides two interfaces that bestow behavior on a credential class that might prove useful. These interfaces are Refreshable and Destroyable.

The javax.security.auth.Refreshable is useful for a credential that requires a refresh of its state (perhaps the credential is valid only for a specific length of time). Three methods are defined on this interface:

boolean isCurrent()

The isCurrent method should return true if the credential is current or return false if it has expired or needs a refresh of its state:

void refresh() throws RefreshFailedException

The refresh method refreshes the current state of the credential, making it valid again. The javax

.security.auth.Destroyable interface gives a credential semantics for destroying its contents:

boolean isDestroyed()

The isDestroyed method returns true if the credential’s contents have been destroyed and returns false otherwise:

void destroy() throws DestroyFailedException

The destroy method destroys the contents of the credential. Methods that require contents to be valid should throw the IllegalStateException after destroy is called.

Authenticating a Subject

The basic manner in which a subject is authenticated is through a LoginContext object. A LoginContext then consults another class for the specific authentication services. The sequence of steps that occurs when a LoginContext is used for authentication is as follows:

1.A LoginContext object is instantiated.

2.The LoginContext consults a Configuration to load all LoginModules for the current application.

3.The login method of the LoginContext is called.

4.Each LoginModule then attempts to authenticate the subject. The LoginModule should associate principals/credentials with a successfully authenticated user.

5.The success or failure of the authentication is communicated back to the application.

Configuration

The configuration file contains a number of configurations per application for authentication. Each configuration has a name (usually the application name) and then a list of login modules to use for authentication. The configuration can have one set of login modules under the name other to specify an authentication scheme to use when no others match the name specified. Each set of login modules adheres to the following syntax:

615

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]