Using random iv vector to generate unique tokens for each login

This commit is contained in:
Martin Stockhammer 2017-02-12 17:09:15 +01:00
parent de5816adf0
commit a6ceb38469
2 changed files with 94 additions and 28 deletions

View File

@ -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) {

View File

@ -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);