/*
This file is part of jpcsp.
Jpcsp is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Jpcsp is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Jpcsp. If not, see <http://www.gnu.org/licenses/>.
*/
package jpcsp.crypto;
import java.io.ByteArrayOutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.Security;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import jpcsp.HLE.Modules;
import org.apache.log4j.Logger;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class AES128 {
private static Logger log = Modules.log;
private static final byte[] const_Zero = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
private static final byte[] const_Rb = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0x87};
private byte[] contentKey;
private ByteArrayOutputStream barros;
private static Cipher cipher;
private static final byte[] iv0 = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
// Do not use Bouncy Castle as the default implementation is much faster
public static final boolean useBouncyCastle = false;
public static void init() {
// Run in a background thread as the initialization is taking around 300 milliseconds
Thread staticInit = new Thread(new Runnable() {
@Override
public void run() {
init("AES/CBC/NoPadding");
}
});
staticInit.start();
}
private static void init(String mode) {
if (cipher == null) {
if (useBouncyCastle) {
Security.addProvider(new BouncyCastleProvider());
}
try {
if (useBouncyCastle) {
cipher = Cipher.getInstance(mode, "BC");
} else {
cipher = Cipher.getInstance(mode);
}
} catch (Exception e) {
log.error("AES128 Cipher", e);
}
}
}
public AES128(String mode) {
init(mode);
}
private Key getKeySpec(byte[] encKey) {
return new SecretKeySpec(encKey, "AES");
}
// Private encrypting method for CMAC (IV == 0).
private byte[] encryptCMAC(byte[] in, byte[] encKey) {
return encryptCMAC(in, getKeySpec(encKey));
}
// Private encrypting method for CMAC (IV == 0).
private byte[] encryptCMAC(byte[] in, Key keySpec) {
return encrypt(in, keySpec, iv0);
}
// Public encrypting/decrypting methods (for CryptoEngine calls).
public byte[] encrypt(byte[] in, byte[] encKey, byte[] iv) {
return encrypt(in, getKeySpec(encKey), iv);
}
// Public encrypting/decrypting methods (for CryptoEngine calls).
public byte[] encrypt(byte[] in, Key keySpec, byte[] iv) {
IvParameterSpec ivec = new IvParameterSpec(iv);
byte[] result = null;
try {
Cipher c = cipher;
c.init(Cipher.ENCRYPT_MODE, keySpec, ivec);
result = c.doFinal(in);
} catch (InvalidKeyException e) {
log.error("encrypt", e);
} catch (InvalidAlgorithmParameterException e) {
log.error("encrypt", e);
} catch (IllegalBlockSizeException e) {
log.error("encrypt", e);
} catch (BadPaddingException e) {
log.error("encrypt", e);
}
return result;
}
public byte[] decrypt(byte[] in, byte[] decKey, byte[] iv) {
Key keySpec = new SecretKeySpec(decKey, "AES");
IvParameterSpec ivec = new IvParameterSpec(iv);
byte[] result = null;
try {
Cipher c = cipher;
c.init(Cipher.DECRYPT_MODE, keySpec, ivec);
result = c.doFinal(in);
} catch (Exception e) {
log.error("decrypt", e);
}
return result;
}
public void doInitCMAC(byte[] contentKey) {
this.contentKey = contentKey;
barros = new ByteArrayOutputStream();
}
public void doUpdateCMAC(byte[] input, int offset, int len) {
barros.write(input, offset, len);
}
public void doUpdateCMAC(byte[] input) {
barros.write(input, 0, input.length);
}
public byte[] doFinalCMAC() {
Object[] keys = generateSubKey(contentKey);
byte[] K1 = (byte[]) keys[0];
byte[] K2 = (byte[]) keys[1];
byte[] input = barros.toByteArray();
int numberOfRounds = (input.length + 15) / 16;
boolean lastBlockComplete;
if (numberOfRounds == 0) {
numberOfRounds = 1;
lastBlockComplete = false;
} else {
if (input.length % 16 == 0) {
lastBlockComplete = true;
} else {
lastBlockComplete = false;
}
}
byte[] M_last;
int srcPos = 16 * (numberOfRounds - 1);
if (lastBlockComplete) {
byte[] partInput = new byte[16];
System.arraycopy(input, srcPos, partInput, 0, 16);
M_last = xor128(partInput, K1);
} else {
byte[] partInput = new byte[input.length % 16];
System.arraycopy(input, srcPos, partInput, 0, input.length % 16);
byte[] padded = doPaddingCMAC(partInput);
M_last = xor128(padded, K2);
}
byte[] X = const_Zero.clone();
byte[] partInput = new byte[16];
byte[] Y;
Key keySpec = getKeySpec(contentKey);
for (int i = 0; i < numberOfRounds - 1; i++) {
srcPos = 16 * i;
System.arraycopy(input, srcPos, partInput, 0, 16);
Y = xor128(partInput, X); /* Y := Mi (+) X */
X = encryptCMAC(Y, keySpec);
}
Y = xor128(X, M_last);
X = encryptCMAC(Y, contentKey);
return X;
}
public boolean doVerifyCMAC(byte[] verificationCMAC) {
byte[] cmac = doFinalCMAC();
if (verificationCMAC == null || verificationCMAC.length != cmac.length) {
return false;
}
for (int i = 0; i < cmac.length; i++) {
if (cmac[i] != verificationCMAC[i]) {
return false;
}
}
return true;
}
private byte[] doPaddingCMAC(byte[] input) {
byte[] padded = new byte[16];
for (int j = 0; j < 16; j++) {
if (j < input.length) {
padded[j] = input[j];
} else if (j == input.length) {
padded[j] = (byte) 0x80;
} else {
padded[j] = (byte) 0x00;
}
}
return padded;
}
private Object[] generateSubKey(byte[] key) {
byte[] L = encryptCMAC(const_Zero, key);
byte[] K1 = null;
if ((L[0] & 0x80) == 0) { /* If MSB(L) = 0, then K1 = L << 1 */
K1 = doLeftShiftOneBit(L);
} else { /* Else K1 = ( L << 1 ) (+) Rb */
byte[] tmp = doLeftShiftOneBit(L);
K1 = xor128(tmp, const_Rb);
}
byte[] K2 = null;
if ((K1[0] & 0x80) == 0) {
K2 = doLeftShiftOneBit(K1);
} else {
byte[] tmp = doLeftShiftOneBit(K1);
K2 = xor128(tmp, const_Rb);
}
Object[] result = new Object[2];
result[0] = K1;
result[1] = K2;
return result;
}
private static byte[] xor128(byte[] input1, byte[] input2) {
byte[] output = new byte[input1.length];
for (int i = 0; i < input1.length; i++) {
output[i] = (byte) (((int) input1[i] ^ (int) input2[i]) & 0xFF);
}
return output;
}
private static byte[] doLeftShiftOneBit(byte[] input) {
byte[] output = new byte[input.length];
byte overflow = 0;
for (int i = (input.length - 1); i >= 0; i--) {
output[i] = (byte) ((int) input[i] << 1 & 0xFF);
output[i] |= overflow;
overflow = ((input[i] & 0x80) != 0) ? (byte) 1 : (byte) 0;
}
return output;
}
}