Using random iv vector to generate unique tokens for each login
This commit is contained in:
parent
de5816adf0
commit
a6ceb38469
|
@ -20,6 +20,7 @@ package org.apache.archiva.redback.authentication;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import org.apache.commons.codec.binary.Base64;
|
import org.apache.commons.codec.binary.Base64;
|
||||||
|
import org.apache.commons.lang.ArrayUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
@ -57,12 +58,12 @@ import java.util.Arrays;
|
||||||
@Service("tokenManager#jce")
|
@Service("tokenManager#jce")
|
||||||
public class TokenManager {
|
public class TokenManager {
|
||||||
|
|
||||||
private static final SecureRandom rd = new SecureRandom();
|
private final ThreadLocal<SecureRandom> rd = new ThreadLocal<SecureRandom>();
|
||||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||||
private String algorithm = "AES/ECB/PKCS5Padding";
|
private String algorithm = "AES/CBC/PKCS5Padding";
|
||||||
private int keySize = -1;
|
private int keySize = -1;
|
||||||
private Cipher deCipher;
|
private int ivSize = -1;
|
||||||
private Cipher enCipher;
|
private SecretKey secretKey;
|
||||||
|
|
||||||
boolean paddingUsed = true;
|
boolean paddingUsed = true;
|
||||||
|
|
||||||
|
@ -71,8 +72,8 @@ public class TokenManager {
|
||||||
public void initialize() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, EncryptionFailedException, InvalidAlgorithmParameterException {
|
public void initialize() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, EncryptionFailedException, InvalidAlgorithmParameterException {
|
||||||
log.debug("Initializing key for token generator");
|
log.debug("Initializing key for token generator");
|
||||||
try {
|
try {
|
||||||
enCipher = Cipher.getInstance(algorithm);
|
rd.set(new SecureRandom());
|
||||||
deCipher = Cipher.getInstance(algorithm);
|
Cipher enCipher = Cipher.getInstance(algorithm);
|
||||||
String[] keyAlg = enCipher.getAlgorithm().split("/");
|
String[] keyAlg = enCipher.getAlgorithm().split("/");
|
||||||
if (keyAlg.length<1) {
|
if (keyAlg.length<1) {
|
||||||
throw new EncryptionFailedException("Initialization of key failed. Not algorithm found.");
|
throw new EncryptionFailedException("Initialization of key failed. Not algorithm found.");
|
||||||
|
@ -85,14 +86,14 @@ public class TokenManager {
|
||||||
if (keyAlg.length==3 && keyAlg[2].equals("NoPadding")) {
|
if (keyAlg.length==3 && keyAlg[2].equals("NoPadding")) {
|
||||||
paddingUsed=false;
|
paddingUsed=false;
|
||||||
}
|
}
|
||||||
SecretKey secretKey = keyGen.generateKey();
|
this.secretKey = keyGen.generateKey();
|
||||||
enCipher.init(Cipher.ENCRYPT_MODE, secretKey);
|
enCipher.init(Cipher.ENCRYPT_MODE, secretKey);
|
||||||
// We have to provide the IV depending on the algorithm used
|
// We have to provide the IV depending on the algorithm used
|
||||||
// CBC needs an IV, ECB not.
|
// CBC needs an IV, ECB not.
|
||||||
if (enCipher.getIV()==null) {
|
if (enCipher.getIV()==null) {
|
||||||
deCipher.init(Cipher.DECRYPT_MODE, secretKey);
|
ivSize=-1;
|
||||||
} else {
|
} else {
|
||||||
deCipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(enCipher.getIV()));
|
ivSize=enCipher.getIV().length;
|
||||||
}
|
}
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
log.error("Error occurred during key initialization. Requested algorithm not available. "+e.getMessage());
|
log.error("Error occurred during key initialization. Requested algorithm not available. "+e.getMessage());
|
||||||
|
@ -103,14 +104,9 @@ public class TokenManager {
|
||||||
} catch (InvalidKeyException e) {
|
} catch (InvalidKeyException e) {
|
||||||
log.error("The key is not valid.");
|
log.error("The key is not valid.");
|
||||||
throw e;
|
throw e;
|
||||||
} catch (InvalidAlgorithmParameterException e) {
|
|
||||||
log.error("Invalid encryption parameters.");
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("SameParameterValue")
|
|
||||||
public String encryptToken(String user, long lifetime) throws EncryptionFailedException {
|
public String encryptToken(String user, long lifetime) throws EncryptionFailedException {
|
||||||
return encryptToken(new SimpleTokenData(user, lifetime, createNonce()));
|
return encryptToken(new SimpleTokenData(user, lifetime, createNonce()));
|
||||||
}
|
}
|
||||||
|
@ -127,6 +123,18 @@ public class TokenManager {
|
||||||
} catch (IllegalBlockSizeException e) {
|
} catch (IllegalBlockSizeException e) {
|
||||||
log.error("Block size invalid");
|
log.error("Block size invalid");
|
||||||
throw new EncryptionFailedException(e);
|
throw new EncryptionFailedException(e);
|
||||||
|
} catch (NoSuchPaddingException e) {
|
||||||
|
log.error("Padding not available "+algorithm);
|
||||||
|
throw new EncryptionFailedException(e);
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
log.error("Bad encryption key");
|
||||||
|
throw new EncryptionFailedException(e);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
log.error("Bad encryption algorithm "+algorithm);
|
||||||
|
throw new EncryptionFailedException(e);
|
||||||
|
} catch (InvalidAlgorithmParameterException e) {
|
||||||
|
log.error("Invalid encryption parameters");
|
||||||
|
throw new EncryptionFailedException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,29 +153,63 @@ public class TokenManager {
|
||||||
} catch (IllegalBlockSizeException ex) {
|
} catch (IllegalBlockSizeException ex) {
|
||||||
log.error("The encrypted token has the wrong block size.");
|
log.error("The encrypted token has the wrong block size.");
|
||||||
throw new InvalidTokenException(ex);
|
throw new InvalidTokenException(ex);
|
||||||
|
} catch (NoSuchPaddingException e) {
|
||||||
|
log.error("Padding not available "+algorithm);
|
||||||
|
throw new InvalidTokenException(e);
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
log.error("Invalid decryption key");
|
||||||
|
throw new InvalidTokenException(e);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
log.error("Encryption algorithm not available "+algorithm);
|
||||||
|
throw new InvalidTokenException(e);
|
||||||
|
} catch (InvalidAlgorithmParameterException e) {
|
||||||
|
log.error("Invalid encryption parameters");
|
||||||
|
throw new InvalidTokenException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private long createNonce() {
|
private long createNonce() {
|
||||||
return rd.nextLong();
|
if (rd.get()==null) {
|
||||||
|
rd.set(new SecureRandom());
|
||||||
|
}
|
||||||
|
return rd.get().nextLong();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected byte[] encrypt(TokenData info) throws IOException, BadPaddingException, IllegalBlockSizeException {
|
protected byte[] encrypt(TokenData info) throws IOException, BadPaddingException, IllegalBlockSizeException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException {
|
||||||
return doEncrypt(convertToByteArray(info));
|
return doEncrypt(convertToByteArray(info), info.getNonce());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected byte[] doEncrypt(byte[] data) throws BadPaddingException, IllegalBlockSizeException {
|
private byte[] getIv(long nonce) {
|
||||||
|
byte[] iv = new byte[ivSize];
|
||||||
|
SecureRandom sr = getRandomGenerator();
|
||||||
|
sr.setSeed(nonce);
|
||||||
|
sr.nextBytes(iv);
|
||||||
|
return iv;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected byte[] doEncrypt(byte[] data, long nonce) throws BadPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
|
||||||
|
Cipher cipher = getEnCipher();
|
||||||
byte[] encData;
|
byte[] encData;
|
||||||
if (!paddingUsed && (data.length % enCipher.getBlockSize())!=0) {
|
byte[] iv;
|
||||||
int blocks = data.length / enCipher.getBlockSize();
|
if (ivSize>0) {
|
||||||
encData = Arrays.copyOf(data, enCipher.getBlockSize()*(blocks+1));
|
iv = getIv(nonce);
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE,this.secretKey,new IvParameterSpec(iv));
|
||||||
|
} else {
|
||||||
|
iv = new byte[0];
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE,this.secretKey);
|
||||||
|
}
|
||||||
|
if (!paddingUsed && (data.length % cipher.getBlockSize())!=0) {
|
||||||
|
int blocks = data.length / cipher.getBlockSize();
|
||||||
|
encData = Arrays.copyOf(data, cipher.getBlockSize()*(blocks+1));
|
||||||
} else {
|
} else {
|
||||||
encData = data;
|
encData = data;
|
||||||
}
|
}
|
||||||
return enCipher.doFinal(encData);
|
byte[] encrypted = cipher.doFinal(encData);
|
||||||
|
// prepending the iv to the token
|
||||||
|
return ArrayUtils.addAll(iv,encrypted);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected TokenData decrypt(byte[] token) throws BadPaddingException, IllegalBlockSizeException, IOException, ClassNotFoundException {
|
protected TokenData decrypt(byte[] token) throws BadPaddingException, IllegalBlockSizeException, IOException, ClassNotFoundException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException {
|
||||||
Object result = convertFromByteArray(doDecrypt(token));
|
Object result = convertFromByteArray(doDecrypt(token));
|
||||||
if (!(result instanceof TokenData)) {
|
if (!(result instanceof TokenData)) {
|
||||||
throw new InvalidClassException("No TokenData found in decrypted token");
|
throw new InvalidClassException("No TokenData found in decrypted token");
|
||||||
|
@ -175,8 +217,31 @@ public class TokenManager {
|
||||||
return (TokenData)result;
|
return (TokenData)result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected byte[] doDecrypt(byte[] encryptedData) throws BadPaddingException, IllegalBlockSizeException {
|
protected byte[] doDecrypt(byte[] encryptedData) throws BadPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException {
|
||||||
return deCipher.doFinal(encryptedData);
|
Cipher cipher = getDeCipher();
|
||||||
|
if (ivSize>0) {
|
||||||
|
byte[] iv = Arrays.copyOfRange(encryptedData,0,ivSize);
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE,this.secretKey,new IvParameterSpec(iv));
|
||||||
|
return cipher.doFinal(encryptedData,ivSize,encryptedData.length-ivSize);
|
||||||
|
} else {
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE,this.secretKey);
|
||||||
|
return cipher.doFinal(encryptedData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SecureRandom getRandomGenerator() {
|
||||||
|
if (rd.get()==null) {
|
||||||
|
rd.set(new SecureRandom());
|
||||||
|
}
|
||||||
|
return rd.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cipher getEnCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
|
||||||
|
return Cipher.getInstance(algorithm);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cipher getDeCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
|
||||||
|
return Cipher.getInstance(algorithm);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String encode(byte[] token) {
|
private String encode(byte[] token) {
|
||||||
|
|
|
@ -27,6 +27,7 @@ import javax.crypto.NoSuchPaddingException;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
@ -48,7 +49,7 @@ public class TokenManagerTest {
|
||||||
public void encryptToken() throws Exception {
|
public void encryptToken() throws Exception {
|
||||||
TokenManager tokenManager = new TokenManager();
|
TokenManager tokenManager = new TokenManager();
|
||||||
tokenManager.initialize();
|
tokenManager.initialize();
|
||||||
assertEquals(tokenManager.getAlgorithm(),"AES/ECB/PKCS5Padding");
|
assertEquals(tokenManager.getAlgorithm(),"AES/CBC/PKCS5Padding");
|
||||||
assertEquals(tokenManager.getKeySize(), -1);
|
assertEquals(tokenManager.getKeySize(), -1);
|
||||||
String token = tokenManager.encryptToken("testuser01",1000);
|
String token = tokenManager.encryptToken("testuser01",1000);
|
||||||
assertNotNull(token);
|
assertNotNull(token);
|
||||||
|
@ -131,7 +132,7 @@ public class TokenManagerTest {
|
||||||
tokenManager.setKeySize(56);
|
tokenManager.setKeySize(56);
|
||||||
tokenManager.initialize();
|
tokenManager.initialize();
|
||||||
byte[] data = { 1, 5, 12, 18, 124, 44, 88, -28, -44};
|
byte[] data = { 1, 5, 12, 18, 124, 44, 88, -28, -44};
|
||||||
byte[] token = tokenManager.doEncrypt(data);
|
byte[] token = tokenManager.doEncrypt(data, new SecureRandom().nextLong());
|
||||||
assertNotNull(token);
|
assertNotNull(token);
|
||||||
byte[] tokenData = tokenManager.doDecrypt(token);
|
byte[] tokenData = tokenManager.doDecrypt(token);
|
||||||
assertNotNull(tokenData);
|
assertNotNull(tokenData);
|
||||||
|
@ -141,7 +142,7 @@ public class TokenManagerTest {
|
||||||
tokenManager.setAlgorithm("AES/CBC/NoPadding");
|
tokenManager.setAlgorithm("AES/CBC/NoPadding");
|
||||||
tokenManager.setKeySize(128);
|
tokenManager.setKeySize(128);
|
||||||
tokenManager.initialize();
|
tokenManager.initialize();
|
||||||
token = tokenManager.doEncrypt(data);
|
token = tokenManager.doEncrypt(data, new SecureRandom().nextLong());
|
||||||
assertNotNull(token);
|
assertNotNull(token);
|
||||||
// Without padding the decrypted value is a multiple of the block size.
|
// Without padding the decrypted value is a multiple of the block size.
|
||||||
tokenData = Arrays.copyOf(tokenManager.doDecrypt(token), data.length);
|
tokenData = Arrays.copyOf(tokenManager.doDecrypt(token), data.length);
|
||||||
|
|
Loading…
Reference in New Issue