SEC-811: Provide a mechanism to allocate and rebuild cryptographically strong, randomised tokens.
This commit is contained in:
parent
a599ef5398
commit
7a2e1e13d3
|
@ -0,0 +1,59 @@
|
|||
package org.springframework.security.token;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The default implementation of {@link Token}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @since 2.0.1
|
||||
*/
|
||||
public class DefaultToken implements Token {
|
||||
private String key;
|
||||
private long keyCreationTime;
|
||||
private String extendedInformation;
|
||||
|
||||
public DefaultToken(String key, long keyCreationTime, String extendedInformation) {
|
||||
Assert.hasText(key, "Key required");
|
||||
Assert.notNull(extendedInformation, "Extended information cannot be null");
|
||||
this.key = key;
|
||||
this.keyCreationTime = keyCreationTime;
|
||||
this.extendedInformation = extendedInformation;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public long getKeyCreationTime() {
|
||||
return keyCreationTime;
|
||||
}
|
||||
|
||||
public String getExtendedInformation() {
|
||||
return extendedInformation;
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if (obj != null && obj instanceof DefaultToken) {
|
||||
DefaultToken rhs = (DefaultToken) obj;
|
||||
return this.key.equals(rhs.key) && this.keyCreationTime == rhs.keyCreationTime && this.extendedInformation.equals(rhs.extendedInformation);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
int code = 979;
|
||||
code = code * key.hashCode();
|
||||
code = code * new Long(keyCreationTime).hashCode();
|
||||
code = code * extendedInformation.hashCode();
|
||||
return code;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "DefaultToken[key=" + new String(key) + "; creation=" + new Date(keyCreationTime) + "; extended=" + extendedInformation + "]";
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
package org.springframework.security.token;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.security.util.Sha512DigestUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Basic implementation of {@link TokenService} that is compatible with clusters and across machine restarts,
|
||||
* without requiring database persistence.
|
||||
*
|
||||
* <p>
|
||||
* Keys are produced in the format:
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Base64(creationTime + ":" + hex(pseudoRandomNumber) + ":" + extendedInformation + ":" +
|
||||
* Sha512Hex(creationTime + ":" + hex(pseudoRandomNumber) + ":" + extendedInformation + ":" + serverSecret) )
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* In the above, <code>creationTime</code>, <code>tokenKey</code> and <code>extendedInformation</code>
|
||||
* are equal to that stored in {@link Token}. The <code>Sha512Hex</code> includes the same payload,
|
||||
* plus a <code>serverSecret</code>.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The <code>serverSecret</code> varies every millisecond. It relies on two static server-side secrets. The first
|
||||
* is a password, and the second is a server integer. Both of these must remain the same for any issued keys
|
||||
* to subsequently be recognised. The applicable <code>serverSecret</code> in any millisecond is computed by
|
||||
* <code>password</code> + ":" + (<code>creationTime</code> % <code>serverInteger</code>). This approach
|
||||
* further obfuscates the actual server secret and renders attempts to compute the server secret more
|
||||
* limited in usefulness (as any false tokens would be forced to have a <code>creationTime</code> equal
|
||||
* to the computed hash). Recall that framework features depending on token services should reject tokens
|
||||
* that are relatively old in any event.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* A further consideration of this class is the requirement for cryptographically strong pseudo-random numbers.
|
||||
* To this end, the use of {@link SecureRandomFactoryBean} is recommended to inject the property.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This implementation uses UTF-8 encoding internally for string manipulation.
|
||||
* </p>
|
||||
*
|
||||
* @author Ben Alex
|
||||
*
|
||||
*/
|
||||
public class KeyBasedPersistenceTokenService implements TokenService, InitializingBean {
|
||||
private int pseudoRandomNumberBits = 256;
|
||||
private String serverSecret;
|
||||
private Integer serverInteger;
|
||||
private SecureRandom secureRandom;
|
||||
|
||||
public Token allocateToken(String extendedInformation) {
|
||||
Assert.notNull(extendedInformation, "Must provided non-null extendedInformation (but it can be empty)");
|
||||
long creationTime = new Date().getTime();
|
||||
String serverSecret = computeServerSecretApplicableAt(creationTime);
|
||||
String pseudoRandomNumber = generatePseudoRandomNumber();
|
||||
String content = new Long(creationTime).toString() + ":" + pseudoRandomNumber + ":" + extendedInformation;
|
||||
|
||||
// Compute key
|
||||
String sha512Hex = Sha512DigestUtils.shaHex(content + ":" + serverSecret);
|
||||
String keyPayload = content + ":" + sha512Hex;
|
||||
String key = convertToString(Base64.encodeBase64(convertToBytes(keyPayload)));
|
||||
|
||||
return new DefaultToken(key, creationTime, extendedInformation);
|
||||
}
|
||||
|
||||
public Token verifyToken(String key) {
|
||||
if (key == null || "".equals(key)) {
|
||||
return null;
|
||||
}
|
||||
String[] tokens = StringUtils.delimitedListToStringArray(convertToString(Base64.decodeBase64(convertToBytes(key))), ":");
|
||||
Assert.isTrue(tokens.length >= 4, "Expected 4 or more tokens but found " + tokens.length);
|
||||
|
||||
long creationTime;
|
||||
try {
|
||||
creationTime = Long.decode(tokens[0]).longValue();
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new IllegalArgumentException("Expected number but found " + tokens[0]);
|
||||
}
|
||||
|
||||
String serverSecret = computeServerSecretApplicableAt(creationTime);
|
||||
String pseudoRandomNumber = tokens[1];
|
||||
|
||||
// Permit extendedInfo to itself contain ":" characters
|
||||
StringBuffer extendedInfo = new StringBuffer();
|
||||
for (int i = 2; i < tokens.length-1; i++) {
|
||||
if (i > 2) {
|
||||
extendedInfo.append(":");
|
||||
}
|
||||
extendedInfo.append(tokens[i]);
|
||||
}
|
||||
|
||||
String sha1Hex = tokens[tokens.length-1];
|
||||
|
||||
// Verification
|
||||
String content = new Long(creationTime).toString() + ":" + pseudoRandomNumber + ":" + extendedInfo.toString();
|
||||
String expectedSha512Hex = Sha512DigestUtils.shaHex(content + ":" + serverSecret);
|
||||
Assert.isTrue(expectedSha512Hex.equals(sha1Hex), "Key verification failure");
|
||||
|
||||
return new DefaultToken(key, creationTime, extendedInfo.toString());
|
||||
}
|
||||
|
||||
private byte[] convertToBytes(String input) {
|
||||
try {
|
||||
return input.getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String convertToString(byte[] bytes) {
|
||||
try {
|
||||
return new String(bytes, "UTF-8");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a pseduo random number (hex encoded)
|
||||
*/
|
||||
private String generatePseudoRandomNumber() {
|
||||
byte[] randomizedBits = new byte[pseudoRandomNumberBits];
|
||||
secureRandom.nextBytes(randomizedBits);
|
||||
return new String(Hex.encodeHex(randomizedBits));
|
||||
}
|
||||
|
||||
private String computeServerSecretApplicableAt(long time) {
|
||||
return serverSecret + ":" + new Long(time % serverInteger.intValue()).intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param serverSecret the new secret, which can contain a ":" if desired (never being sent to the client)
|
||||
*/
|
||||
public void setServerSecret(String serverSecret) {
|
||||
this.serverSecret = serverSecret;
|
||||
}
|
||||
|
||||
public void setSecureRandom(SecureRandom secureRandom) {
|
||||
this.secureRandom = secureRandom;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pseudoRandomNumberBits changes the number of bits issued (must be >= 0; defaults to 256)
|
||||
*/
|
||||
public void setPseudoRandomNumberBits(int pseudoRandomNumberBits) {
|
||||
Assert.isTrue(pseudoRandomNumberBits >= 0, "Must have a positive pseudo random number bit size");
|
||||
this.pseudoRandomNumberBits = pseudoRandomNumberBits;
|
||||
}
|
||||
|
||||
public void setServerInteger(Integer serverInteger) {
|
||||
this.serverInteger = serverInteger;
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.hasText(serverSecret, "Server secret required");
|
||||
Assert.notNull(serverInteger, "Server integer required");
|
||||
Assert.notNull(secureRandom, "SecureRandom instance required");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package org.springframework.security.token;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
/**
|
||||
* Creates a {@link SecureRandom} instance.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @since 2.0.1
|
||||
*
|
||||
*/
|
||||
public class SecureRandomFactoryBean implements FactoryBean {
|
||||
|
||||
private String algorithm = "SHA1PRNG";
|
||||
private Resource seed;
|
||||
|
||||
public Object getObject() throws Exception {
|
||||
SecureRandom rnd = SecureRandom.getInstance(algorithm);
|
||||
|
||||
if (seed != null) {
|
||||
// Seed specified, so use it
|
||||
byte[] seedBytes = FileCopyUtils.copyToByteArray(seed.getInputStream());
|
||||
rnd.setSeed(seedBytes);
|
||||
} else {
|
||||
// Request the next bytes, thus eagerly incurring the expense of default seeding
|
||||
rnd.nextBytes(new byte[1]);
|
||||
}
|
||||
|
||||
return rnd;
|
||||
}
|
||||
|
||||
public Class getObjectType() {
|
||||
return SecureRandom.class;
|
||||
}
|
||||
|
||||
public boolean isSingleton() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the Pseudo Random Number Generator (PRNG) algorithm to be nominated. Defaults to
|
||||
* SHA1PRNG.
|
||||
*
|
||||
* @param algorithm to use (mandatory)
|
||||
*/
|
||||
public void setAlgorithm(String algorithm) {
|
||||
Assert.hasText(algorithm, "Algorithm required");
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the user to specify a resource which will act as a seed for the {@link SecureRandom}
|
||||
* instance. Specifically, the resource will be read into an {@link InputStream} and those
|
||||
* bytes presented to the {@link SecureRandom#setSeed(byte[])} method. Note that this will
|
||||
* simply supplement, rather than replace, the existing seed. As such, it is always safe to
|
||||
* set a seed using this method (it never reduces randomness).
|
||||
*
|
||||
* @param seed to use, or <code>null</code> if no additional seeding is needed
|
||||
*/
|
||||
public void setSeed(Resource seed) {
|
||||
this.seed = seed;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package org.springframework.security.token;
|
||||
|
||||
|
||||
/**
|
||||
* A token issued by {@link TokenService}.
|
||||
*
|
||||
* <p>
|
||||
* It is important that the keys assigned to tokens are sufficiently randomised and secured that
|
||||
* they can serve as identifying a unique user session. Implementations of {@link TokenService}
|
||||
* are free to use encryption or encoding strategies of their choice. It is strongly recommended that
|
||||
* keys are of sufficient length to balance safety against persistence cost. In relation to persistence
|
||||
* cost, it is strongly recommended that returned keys are small enough for encoding in a cookie.
|
||||
* </p>
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @since 2.0.1
|
||||
*/
|
||||
public interface Token {
|
||||
|
||||
/**
|
||||
* Obtains the randomised, secure key assigned to this token. Presentation of this token to
|
||||
* {@link TokenService} will always return a <code>Token</code> that is equal to the original
|
||||
* <code>Token</code> issued for that key.
|
||||
*
|
||||
* @return a key with appropriate randomness and security.
|
||||
*/
|
||||
String getKey();
|
||||
|
||||
/**
|
||||
* The time the token key was initially created is available from this method. Note that a given
|
||||
* token must never have this creation time changed. If necessary, a new token can be
|
||||
* requested from the {@link TokenService} to replace the original token.
|
||||
*
|
||||
* @return the time this token key was created, in the same format as specified by {@link Date#getTime()).
|
||||
*/
|
||||
long getKeyCreationTime();
|
||||
|
||||
/**
|
||||
* Obtains the extended information associated within the token, which was presented when the token
|
||||
* was first created.
|
||||
*
|
||||
* @return the user-specified extended information, if any
|
||||
*/
|
||||
String getExtendedInformation();
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package org.springframework.security.token;
|
||||
|
||||
|
||||
/**
|
||||
* Provides a mechanism to allocate and rebuild secure, randomised tokens.
|
||||
*
|
||||
* <p>
|
||||
* Implementations are solely concern with issuing a new {@link Token} on demand. The
|
||||
* issued <code>Token</code> may contain user-specified extended information. The token also
|
||||
* contains a cryptographically strong, byte array-based key. This permits the token to be
|
||||
* used to identify a user session, if desired. The key can subsequently be re-presented
|
||||
* to the <code>TokenService</code> for verification and reconstruction of a <code>Token</code>
|
||||
* equal to the original <code>Token</code>.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Given the tightly-focused behaviour provided by this interface, it can serve as a building block
|
||||
* for more sophisticated token-based solutions. For example, authentication systems that depend on
|
||||
* stateless session keys. These could, for instance, place the username inside the user-specified
|
||||
* extended information associated with the key). It is important to recognise that we do not intend
|
||||
* for this interface to be expanded to provide such capabilities directly.
|
||||
* </p>
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @since 2.0.1
|
||||
*
|
||||
*/
|
||||
public interface TokenService {
|
||||
/**
|
||||
* Forces the allocation of a new {@link Token}.
|
||||
*
|
||||
* @param the extended information desired in the token (cannot be <code>null</code>, but can be empty)
|
||||
* @return a new token that has not been issued previously, and is guaranteed to be recognised
|
||||
* by this implementation's {@link #verifyToken(String)} at any future time.
|
||||
*/
|
||||
Token allocateToken(String extendedInformation);
|
||||
|
||||
/**
|
||||
* Permits verification the <{@link Token#getKey()} was issued by this <code>TokenService</code> and
|
||||
* reconstructs the corresponding <code>Token</code>.
|
||||
*
|
||||
* @param key as obtained from {@link Token#getKey()} and created by this implementation
|
||||
* @return the token, or <code>null</code> if the token was not issued by this <code>TokenService</code>
|
||||
*/
|
||||
Token verifyToken(String key);
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package org.springframework.security.token;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests {@link DefaultToken}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
*
|
||||
*/
|
||||
public class DefaultTokenTests {
|
||||
@Test
|
||||
public void testEquality() {
|
||||
String key = "key";
|
||||
long created = new Date().getTime();
|
||||
String extendedInformation = "extended";
|
||||
|
||||
DefaultToken t1 = new DefaultToken(key, created, extendedInformation);
|
||||
DefaultToken t2 = new DefaultToken(key, created, extendedInformation);
|
||||
Assert.assertEquals(t1, t2);
|
||||
}
|
||||
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void testRejectsNullExtendedInformation() {
|
||||
String key = "key";
|
||||
long created = new Date().getTime();
|
||||
new DefaultToken(key, created, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqualityWithDifferentExtendedInformation3() {
|
||||
String key = "key";
|
||||
long created = new Date().getTime();
|
||||
|
||||
DefaultToken t1 = new DefaultToken(key, created, "length1");
|
||||
DefaultToken t2 = new DefaultToken(key, created, "longerLength2");
|
||||
Assert.assertFalse(t1.equals(t2));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
|
||||
|
||||
package org.springframework.security.token;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Date;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests {@link KeyBasedPersistenceTokenService}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
*
|
||||
*/
|
||||
public class KeyBasedPersistenceTokenServiceTests {
|
||||
|
||||
private KeyBasedPersistenceTokenService getService() {
|
||||
SecureRandomFactoryBean fb = new SecureRandomFactoryBean();
|
||||
KeyBasedPersistenceTokenService service = new KeyBasedPersistenceTokenService();
|
||||
service.setServerSecret("MY:SECRET$$$#");
|
||||
service.setServerInteger(new Integer(454545));
|
||||
try {
|
||||
SecureRandom rnd = (SecureRandom) fb.getObject();
|
||||
service.setSecureRandom(rnd);
|
||||
service.afterPropertiesSet();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOperationWithSimpleExtendedInformation() {
|
||||
KeyBasedPersistenceTokenService service = getService();
|
||||
Token token = service.allocateToken("Hello world");
|
||||
Token result = service.verifyToken(token.getKey());
|
||||
Assert.assertEquals(token, result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testOperationWithComplexExtendedInformation() {
|
||||
KeyBasedPersistenceTokenService service = getService();
|
||||
Token token = service.allocateToken("Hello:world:::");
|
||||
Token result = service.verifyToken(token.getKey());
|
||||
Assert.assertEquals(token, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOperationWithEmptyRandomNumber() {
|
||||
KeyBasedPersistenceTokenService service = getService();
|
||||
service.setPseudoRandomNumberBits(0);
|
||||
Token token = service.allocateToken("Hello:world:::");
|
||||
Token result = service.verifyToken(token.getKey());
|
||||
Assert.assertEquals(token, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOperationWithNoExtendedInformation() {
|
||||
KeyBasedPersistenceTokenService service = getService();
|
||||
Token token = service.allocateToken("");
|
||||
Token result = service.verifyToken(token.getKey());
|
||||
Assert.assertEquals(token, result);
|
||||
}
|
||||
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void testOperationWithMissingKey() {
|
||||
KeyBasedPersistenceTokenService service = getService();
|
||||
Token token = new DefaultToken("", new Date().getTime(), "");
|
||||
service.verifyToken(token.getKey());
|
||||
}
|
||||
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void testOperationWithTamperedKey() {
|
||||
KeyBasedPersistenceTokenService service = getService();
|
||||
Token goodToken = service.allocateToken("");
|
||||
String fake = goodToken.getKey().toUpperCase();
|
||||
Token token = new DefaultToken(fake, new Date().getTime(), "");
|
||||
service.verifyToken(token.getKey());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package org.springframework.security.token;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
/**
|
||||
* Tests {@link SecureRandomFactoryBean}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
*
|
||||
*/
|
||||
public class SecureRandomFactoryBeanTests {
|
||||
@Test
|
||||
public void testObjectType() {
|
||||
SecureRandomFactoryBean factory = new SecureRandomFactoryBean();
|
||||
Assert.assertEquals(SecureRandom.class, factory.getObjectType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsSingleton() {
|
||||
SecureRandomFactoryBean factory = new SecureRandomFactoryBean();
|
||||
Assert.assertFalse(factory.isSingleton());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatesUsingDefaults() throws Exception {
|
||||
SecureRandomFactoryBean factory = new SecureRandomFactoryBean();
|
||||
Object result = factory.getObject();
|
||||
Assert.assertTrue(result instanceof SecureRandom);
|
||||
int rnd = ((SecureRandom)result).nextInt();
|
||||
Assert.assertTrue(rnd != 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatesUsingSeed() throws Exception {
|
||||
SecureRandomFactoryBean factory = new SecureRandomFactoryBean();
|
||||
Resource resource = new ClassPathResource("org/springframework/security/token/SecureRandomFactoryBeanTests.class");
|
||||
Assert.assertNotNull(resource);
|
||||
factory.setSeed(resource);
|
||||
Object result = factory.getObject();
|
||||
Assert.assertTrue(result instanceof SecureRandom);
|
||||
int rnd = ((SecureRandom)result).nextInt();
|
||||
Assert.assertTrue(rnd != 0);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package org.springframework.security.util;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
|
||||
/**
|
||||
* Provides SHA512 digest methods.
|
||||
*
|
||||
* <p>
|
||||
* Based on Commons Codec, which does not presently provide SHA512 support.
|
||||
* </p>
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @since 2.0.1
|
||||
*
|
||||
*/
|
||||
public abstract class Sha512DigestUtils {
|
||||
/**
|
||||
* Returns a MessageDigest for the given <code>algorithm</code>.
|
||||
*
|
||||
* @param algorithm The MessageDigest algorithm name.
|
||||
* @return An MD5 digest instance.
|
||||
* @throws RuntimeException when a {@link java.security.NoSuchAlgorithmException} is caught,
|
||||
*/
|
||||
static MessageDigest getDigest(String algorithm) {
|
||||
try {
|
||||
return MessageDigest.getInstance(algorithm);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an SHA digest.
|
||||
*
|
||||
* @return An SHA digest instance.
|
||||
* @throws RuntimeException when a {@link java.security.NoSuchAlgorithmException} is caught,
|
||||
*/
|
||||
private static MessageDigest getSha512Digest() {
|
||||
return getDigest("SHA-512");
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the SHA digest and returns the value as a
|
||||
* <code>byte[]</code>.
|
||||
*
|
||||
* @param data Data to digest
|
||||
* @return SHA digest
|
||||
*/
|
||||
public static byte[] sha(byte[] data) {
|
||||
return getSha512Digest().digest(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the SHA digest and returns the value as a
|
||||
* <code>byte[]</code>.
|
||||
*
|
||||
* @param data Data to digest
|
||||
* @return SHA digest
|
||||
*/
|
||||
public static byte[] sha(String data) {
|
||||
return sha(data.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the SHA digest and returns the value as a hex string.
|
||||
*
|
||||
* @param data Data to digest
|
||||
* @return SHA digest as a hex string
|
||||
*/
|
||||
public static String shaHex(byte[] data) {
|
||||
return new String(Hex.encodeHex(sha(data)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the SHA digest and returns the value as a hex string.
|
||||
*
|
||||
* @param data Data to digest
|
||||
* @return SHA digest as a hex string
|
||||
*/
|
||||
public static String shaHex(String data) {
|
||||
return new String(Hex.encodeHex(sha(data)));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue