/*
* Copyright (c) Christopher A Longo
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.chrislongo.hls;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.URL;
import java.net.URLConnection;
import java.security.Security;
public class Crypto {
private Cipher cipher;
private String baseUrl;
private String keyUrl;
private byte[] key;
private byte[] iv;
boolean forceKey = false;
public Crypto(String baseUrl) {
this(baseUrl, null);
}
public Crypto(String baseUrl, String key) {
this.baseUrl = baseUrl;
if (key != null) {
this.key = unpackHexString(key);
forceKey = true;
}
}
public CipherInputStream wrapInputStream(InputStream in) {
return new CipherInputStream(in, cipher);
}
public boolean hasKey() {
return (key != null);
}
public String getCurrentKey() {
return arrayToString(key);
}
public String getCurrentIV() {
return arrayToString(iv);
}
public void updateKeyString(String keyString) throws CryptoException {
String[] parts = keyString.split(",");
String newKeyUrl = parts[1].split("\"")[1];
if (parts.length > 2) {
String ivString = parts[2].split("0x")[1];
iv = unpackHexString(ivString);
} else {
iv = new byte[16];
}
try {
if (!forceKey && !newKeyUrl.equals(keyUrl)) {
if (newKeyUrl.startsWith("http")) {
keyUrl = newKeyUrl;
} else {
keyUrl = baseUrl + newKeyUrl;
}
fetchKey();
}
} catch (IOException e) {
throw new CryptoException(e);
}
initCipher();
}
private void fetchKey() throws IOException {
this.key = new byte[16];
URLConnection con = new URL(this.keyUrl).openConnection();
InputStream in = new BufferedInputStream(con.getInputStream());
int read = 0;
int offset = 0;
while (offset < 16) {
if ((read = in.read(this.key, offset, key.length - offset)) == -1)
break;
offset += read;
}
in.close();
}
private void initCipher() throws CryptoException {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
try {
SecretKey secretKey = new SecretKeySpec(key, "AES");
cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
} catch (Exception e) {
throw new CryptoException(e);
}
}
private byte[] unpackHexString(String hexString) {
byte[] array = new byte[16];
BigInteger bigInt = new BigInteger(hexString, 16);
BigInteger andValue = new BigInteger("ff", 16);
for (int i = 0, index = 15; i < 16; i++, index--) {
BigInteger shift = bigInt.shiftRight(i * 8);
shift = shift.and(andValue);
array[index] = shift.byteValue();
}
return array;
}
private String arrayToString(byte[] array) {
StringBuilder sb = new StringBuilder();
if (array != null) {
for (byte b : array)
sb.append(String.format("%02x", b));
}
return sb.toString();
}
@Override
public String toString() {
return "net.chrislongo.hls.Crypto{" +
"cipher=" + cipher +
", keyUrl='" + keyUrl + '\'' +
", key=" + arrayToString(key) +
", iv=" + arrayToString(iv) +
'}';
}
}