This is actually exactly what I am looking for but in Java:
Encrypted cookies in Chrome
The mentioned "Windows Data Protection API (DPAPI)" I have found here for Java:
http://jdpapi.sourceforge.net/
but its "outdated" or only for 32bit platform.
SQLite connection and result is of course working that's what I got atm:
public void getDecryptedValue() {
try {
Statement stmt = connection.createStatement();
String sql = "SELECT * FROM cookies";
ResultSet rs = stmt.executeQuery(sql);
int cookieCount = 1;
while (rs.next()) {
log.debug("####### Cookie " + cookieCount + " ############");
String host_key = rs.getString("host_key");
log.debug(host_key);
byte[] encrypted_value = rs.getBytes("encrypted_value");
//this ofc outputs "nonsense". here the decryption is needed.
log.debug(encrypted_value.toString());
cookieCount++;
}
rs.close();
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
I searched now for 2 days found nothing helpful my next approach would be to
decrypt/encrypt triple DES encryption with (Windows username? and password? what is used as seed if you have no password?) but I really don't know how I should do that with the BLOB I get from the SQLite DB or even start.
Edit: I am digging deeper with that atm : http://n8henrie.com/2014/05/decrypt-chrome-cookies-with-python/
I spent a long time on this myself. Here are some classes you can use to decrypt cookies for Chrome on Mac, Windows, and Linux. I didn't implement inserting cookies, but I imagine you have most of everything you need with this code to add that functionality.
It requires some third party libraries (JNI Windows encryption wrappers and SQLite) so I am linking to the Maven project I am using the code in if you need to see the code in context.
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.file.Files;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import com.sun.jna.platform.win32.Crypt32Util;
public class Test {
public static void main(String[] args) throws IOException {
ChromeBrowser chrome = new ChromeBrowser();
Set<Cookie> cookies = chrome.getCookiesForDomain("stackoverflow.com");
for(Cookie cookie : cookies){
System.out.println(cookie.toString());
}
}
public static abstract class Cookie {
protected String name;
protected byte[] encryptedValue;
protected Date expires;
protected String path;
protected String domain;
protected boolean secure;
protected boolean httpOnly;
protected File cookieStore;
public Cookie(String name, byte[] encryptedValue, Date expires, String path, String domain, boolean secure, boolean httpOnly, File cookieStore) {
this.name = name;
this.encryptedValue = encryptedValue;
this.expires = expires;
this.path = path;
this.domain = domain;
this.secure = secure;
this.httpOnly = httpOnly;
this.cookieStore = cookieStore;
}
public String getName() {
return name;
}
public byte[] getEncryptedValue() {
return encryptedValue;
}
public Date getExpires() {
return expires;
}
public String getPath() {
return path;
}
public String getDomain() {
return domain;
}
public boolean isSecure() {
return secure;
}
public boolean isHttpOnly() {
return httpOnly;
}
public File getCookieStore(){
return cookieStore;
}
public abstract boolean isDecrypted();
}
public static class DecryptedCookie extends Cookie {
private String decryptedValue;
public DecryptedCookie(String name, byte[] encryptedValue, String decryptedValue, Date expires, String path, String domain, boolean secure, boolean httpOnly, File cookieStore) {
super(name, encryptedValue, expires, path, domain, secure, httpOnly, cookieStore);
this.decryptedValue = decryptedValue;
}
public String getDecryptedValue(){
return decryptedValue;
}
#Override
public boolean isDecrypted() {
return true;
}
#Override
public String toString() {
return "Cookie [name=" + name + ", value=" + decryptedValue + "]";
}
}
public static class EncryptedCookie extends Cookie {
public EncryptedCookie(String name, byte[] encryptedValue, Date expires, String path, String domain, boolean secure, boolean httpOnly, File cookieStore) {
super(name, encryptedValue, expires, path, domain, secure, httpOnly, cookieStore);
}
#Override
public boolean isDecrypted() {
return false;
}
#Override
public String toString() {
return "Cookie [name=" + name + " (encrypted)]";
}
}
public static class OS {
public static String getOsArchitecture() {
return System.getProperty("os.arch");
}
public static String getOperatingSystem() {
return System.getProperty("os.name");
}
public static String getOperatingSystemVersion() {
return System.getProperty("os.version");
}
public static String getIP() throws UnknownHostException {
InetAddress ip = InetAddress.getLocalHost();
return ip.getHostAddress();
}
public static String getHostname() throws UnknownHostException {
return InetAddress.getLocalHost().getHostName();
}
public static boolean isWindows() {
return (getOperatingSystem().toLowerCase().indexOf("win") >= 0);
}
public static boolean isMac() {
return (getOperatingSystem().toLowerCase().indexOf("mac") >= 0);
}
public static boolean isLinux() {
return (getOperatingSystem().toLowerCase().indexOf("nix") >= 0 || getOperatingSystem().toLowerCase().indexOf("nux") >= 0 || getOperatingSystem().toLowerCase().indexOf("aix") > 0 );
}
public static boolean isSolaris() {
return (getOperatingSystem().toLowerCase().indexOf("sunos") >= 0);
}
}
public static abstract class Browser {
/**
* A file that should be used to make a temporary copy of the browser's cookie store
*/
protected File cookieStoreCopy = new File(".cookies.db");
/**
* Returns all cookies
*/
public Set<Cookie> getCookies() {
HashSet<Cookie> cookies = new HashSet<Cookie>();
for(File cookieStore : getCookieStores()){
cookies.addAll(processCookies(cookieStore, null));
}
return cookies;
}
/**
* Returns cookies for a given domain
*/
public Set<Cookie> getCookiesForDomain(String domain) {
HashSet<Cookie> cookies = new HashSet<Cookie>();
for(File cookieStore : getCookieStores()){
cookies.addAll(processCookies(cookieStore, domain));
}
return cookies;
}
/**
* Returns a set of cookie store locations
* #return
*/
protected abstract Set<File> getCookieStores();
/**
* Processes all cookies in the cookie store for a given domain or all
* domains if domainFilter is null
*
* #param cookieStore
* #param domainFilter
* #return
*/
protected abstract Set<Cookie> processCookies(File cookieStore, String domainFilter);
/**
* Decrypts an encrypted cookie
* #param encryptedCookie
* #return
*/
protected abstract DecryptedCookie decrypt(EncryptedCookie encryptedCookie);
}
/**
* An implementation of Chrome cookie decryption logic for Mac, Windows, and Linux installs
*
* References:
* 1) http://n8henrie.com/2014/05/decrypt-chrome-cookies-with-python/
* 2) https://github.com/markushuber/ssnoob
*
* #author Ben Holland
*/
public static class ChromeBrowser extends Browser {
private String chromeKeyringPassword = null;
/**
* Returns a set of cookie store locations
* #return
*/
#Override
protected Set<File> getCookieStores() {
HashSet<File> cookieStores = new HashSet<File>();
// pre Win7
cookieStores.add(new File(System.getProperty("user.home") + "\\Application Data\\Google\\Chrome\\User Data\\Default\\Cookies"));
// Win 7+
cookieStores.add(new File(System.getProperty("user.home") + "\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Cookies"));
// Mac
cookieStores.add(new File(System.getProperty("user.home") + "/Library/Application Support/Google/Chrome/Default/Cookies"));
// Linux
cookieStores.add(new File(System.getProperty("user.home") + "/.config/chromium/Default/Cookies"));
return cookieStores;
}
/**
* Processes all cookies in the cookie store for a given domain or all
* domains if domainFilter is null
*
* #param cookieStore
* #param domainFilter
* #return
*/
#Override
protected Set<Cookie> processCookies(File cookieStore, String domainFilter) {
HashSet<Cookie> cookies = new HashSet<Cookie>();
if(cookieStore.exists()){
Connection connection = null;
try {
cookieStoreCopy.delete();
Files.copy(cookieStore.toPath(), cookieStoreCopy.toPath());
// load the sqlite-JDBC driver using the current class loader
Class.forName("org.sqlite.JDBC");
// create a database connection
connection = DriverManager.getConnection("jdbc:sqlite:" + cookieStoreCopy.getAbsolutePath());
Statement statement = connection.createStatement();
statement.setQueryTimeout(30); // set timeout to 30 seconds
ResultSet result = null;
if(domainFilter == null || domainFilter.isEmpty()){
result = statement.executeQuery("select * from cookies");
} else {
result = statement.executeQuery("select * from cookies where host_key like \"%" + domainFilter + "%\"");
}
while (result.next()) {
String name = result.getString("name");
byte[] encryptedBytes = result.getBytes("encrypted_value");
String path = result.getString("path");
String domain = result.getString("host_key");
boolean secure = result.getBoolean("secure");
boolean httpOnly = result.getBoolean("httponly");
Date expires = result.getDate("expires_utc");
EncryptedCookie encryptedCookie = new EncryptedCookie(name,
encryptedBytes,
expires,
path,
domain,
secure,
httpOnly,
cookieStore);
DecryptedCookie decryptedCookie = decrypt(encryptedCookie);
if(decryptedCookie != null){
cookies.add(decryptedCookie);
} else {
cookies.add(encryptedCookie);
}
cookieStoreCopy.delete();
}
} catch (Exception e) {
e.printStackTrace();
// if the error message is "out of memory",
// it probably means no database file is found
} finally {
try {
if (connection != null){
connection.close();
}
} catch (SQLException e) {
// connection close failed
}
}
}
return cookies;
}
/**
* Decrypts an encrypted cookie
* #param encryptedCookie
* #return
*/
#Override
protected DecryptedCookie decrypt(EncryptedCookie encryptedCookie) {
byte[] decryptedBytes = null;
if(OS.isWindows()){
try {
decryptedBytes = Crypt32Util.cryptUnprotectData(encryptedCookie.getEncryptedValue());
} catch (Exception e){
decryptedBytes = null;
}
} else if(OS.isLinux()){
try {
byte[] salt = "saltysalt".getBytes();
char[] password = "peanuts".toCharArray();
char[] iv = new char[16];
Arrays.fill(iv, ' ');
int keyLength = 16;
int iterations = 1;
PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, keyLength * 8);
SecretKeyFactory pbkdf2 = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] aesKey = pbkdf2.generateSecret(spec).getEncoded();
SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(new String(iv).getBytes()));
// if cookies are encrypted "v10" is a the prefix (has to be removed before decryption)
byte[] encryptedBytes = encryptedCookie.getEncryptedValue();
if (new String(encryptedCookie.getEncryptedValue()).startsWith("v10")) {
encryptedBytes = Arrays.copyOfRange(encryptedBytes, 3, encryptedBytes.length);
}
decryptedBytes = cipher.doFinal(encryptedBytes);
} catch (Exception e) {
decryptedBytes = null;
}
} else if(OS.isMac()){
// access the decryption password from the keyring manager
if(chromeKeyringPassword == null){
try {
chromeKeyringPassword = getMacKeyringPassword("Chrome Safe Storage");
} catch (IOException e) {
decryptedBytes = null;
}
}
try {
byte[] salt = "saltysalt".getBytes();
char[] password = chromeKeyringPassword.toCharArray();
char[] iv = new char[16];
Arrays.fill(iv, ' ');
int keyLength = 16;
int iterations = 1003;
PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, keyLength * 8);
SecretKeyFactory pbkdf2 = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] aesKey = pbkdf2.generateSecret(spec).getEncoded();
SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(new String(iv).getBytes()));
// if cookies are encrypted "v10" is a the prefix (has to be removed before decryption)
byte[] encryptedBytes = encryptedCookie.getEncryptedValue();
if (new String(encryptedCookie.getEncryptedValue()).startsWith("v10")) {
encryptedBytes = Arrays.copyOfRange(encryptedBytes, 3, encryptedBytes.length);
}
decryptedBytes = cipher.doFinal(encryptedBytes);
} catch (Exception e) {
decryptedBytes = null;
}
}
if(decryptedBytes == null){
return null;
} else {
return new DecryptedCookie(encryptedCookie.getName(),
encryptedCookie.getEncryptedValue(),
new String(decryptedBytes),
encryptedCookie.getExpires(),
encryptedCookie.getPath(),
encryptedCookie.getDomain(),
encryptedCookie.isSecure(),
encryptedCookie.isHttpOnly(),
encryptedCookie.getCookieStore());
}
}
/**
* Accesses the apple keyring to retrieve the Chrome decryption password
* #param application
* #return
* #throws IOException
*/
private static String getMacKeyringPassword(String application) throws IOException {
Runtime rt = Runtime.getRuntime();
String[] commands = {"security", "find-generic-password","-w", "-s", application};
Process proc = rt.exec(commands);
BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));
String result = "";
String s = null;
while ((s = stdInput.readLine()) != null) {
result += s;
}
return result;
}
}
}
Related
I have Escrypt Smart Card to sign data bytes and get signature and certificate for it.
I have java tool to do it and everything was fine until Java 8. Now application migrated to Java 11. And problem start from here.
SunPKCS11.jar/ library is not a part of Java 11, hence application breaks.
I tried with different solution available over internet and oracle release notes, it suggest to use java.security package for cryptographic operation.
I am not able to switch from Java 8 to java 11, any support would be really useful.
Edit-1: Minimum usable code
Signature calculator is used to parse signature
It is written with Java 8 & SunPKCS11.jar want to migrate it to Java 11
'''
import java.io.IOException;
import java.util.Scanner;
import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
import sun.security.pkcs11.wrapper.CK_ATTRIBUTE;
import sun.security.pkcs11.wrapper.CK_MECHANISM;
import sun.security.pkcs11.wrapper.CK_RSA_PKCS_PSS_PARAMS;
import sun.security.pkcs11.wrapper.PKCS11;
import sun.security.pkcs11.wrapper.PKCS11Constants;
import sun.security.pkcs11.wrapper.PKCS11Exception;
public class Application {
public static void main(String[] args) {
try {
// PKCS11 middleware (UBK PKI) path
String libraryPath = "";
OpenScPkcs11 openScPkcs11 = new OpenScPkcs11(libraryPath);
openScPkcs11.login("", "");
byte[] hash = null;
String keypath = null;
String oid = null;
String authbits = null;
openScPkcs11.calcSign(hash, keypath, oid, authbits);
}
catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class OpenScPkcs11 {
private static HexBinaryAdapter hexBinaryAdapter = new HexBinaryAdapter();
private int slotId = 0x3;
private String libraryPath;
private PKCS11 pkcs11Instance;
private String signatureAlgo = "RSA";
private long session = -1;
private boolean isLoginDone = false;
private SignatureMechanism.Algorithms algorithm = SignatureMechanism.Algorithms.SHA256;
public OpenScPkcs11(String libraryPath) throws IOException, PKCS11Exception {
this.libraryPath = libraryPath;
initializeMiddleware();
}
public void calcSign(byte[] hash, String keyPath, String userOid, String authbits) throws Exception {
byte[] signature = new byte[512];
if (this.pkcs11Instance != null) {
if (this.session < 0) {
this.openSession();
}
if (!this.isLoginDone) {
this.login("", "");
}
Mechanism mechanism = SignatureMechanism.getMechanism(this.algorithm);
CK_ATTRIBUTE[] pTemplate = new CK_ATTRIBUTE[3];
pTemplate[0] = new CK_ATTRIBUTE(PKCS11Constants.CKA_CLASS, PKCS11Constants.CKO_PRIVATE_KEY);
pTemplate[1] = new CK_ATTRIBUTE(PKCS11Constants.CKA_VENDOR_DEFINED + 1, keyPath);
pTemplate[2] = new CK_ATTRIBUTE(PKCS11Constants.CKA_VENDOR_DEFINED + 2, authbits);
// define the attibutes for certificate
CK_ATTRIBUTE[] certAttribute = new CK_ATTRIBUTE[1];
certAttribute[0] = new CK_ATTRIBUTE(PKCS11Constants.CKA_VENDOR_DEFINED + 3);
long[] c_FindObjects = null;
this.pkcs11Instance.C_FindObjectsInit(this.session, pTemplate);
c_FindObjects = this.pkcs11Instance.C_FindObjects(this.session, 32);
this.pkcs11Instance.C_FindObjectsFinal(this.session);
CK_MECHANISM pMechanism = null;
// RSA Algorithm
String signatureAlgorithmType = this.getSignatureAlgorithmType();
if (signatureAlgorithmType.equalsIgnoreCase("RSA")) {
pMechanism = new CK_MECHANISM(mechanism.getId());
CK_RSA_PKCS_PSS_PARAMS ck_RSA_PKCS_PSS_PARAMS =
new CK_RSA_PKCS_PSS_PARAMS(this.algorithm.name(), "MGF1", this.algorithm.name(), (int) mechanism.getsLen());
pMechanism.pParameter = ck_RSA_PKCS_PSS_PARAMS;
}
else if (signatureAlgorithmType.equalsIgnoreCase("ECDSA")) { // ECDSA Algorithm
pMechanism = new CK_MECHANISM(PKCS11Constants.CKM_ECDSA);
}
else {
throw new Exception("Signature algorithm " + signatureAlgorithmType + " is not supported");
}
if ((c_FindObjects != null) && (c_FindObjects.length > 0)) {
long c_FindObjectFound = c_FindObjects[0];
boolean objFound = false;
for (long c_FindObject : c_FindObjects) {
this.pkcs11Instance.C_GetAttributeValue(this.session, c_FindObject, certAttribute);
// Binary certificate as byte array
byte[] certificateBytes = certAttribute[0].getByteArray();
if ((userOid != null) && !userOid.isEmpty()) {
// Match certificate with userOid, if matches (Certificate parser used)
if (parseOidInfo(certificateBytes, userOid)) {
c_FindObjectFound = c_FindObject;
objFound = true;
break;
}
}
}
if (objFound) {
System.out.println("Signature found for given OID configuration.");
}
else {
this.pkcs11Instance.C_GetAttributeValue(this.session, c_FindObjectFound, certAttribute);
CertificateParser certificateParser = new CertificateParser(certAttribute[0].getByteArray());
}
this.pkcs11Instance.C_SignInit(this.session, pMechanism, c_FindObjectFound);
this.pkcs11Instance.C_SignUpdate(this.session, 0, hash, 0, (int) mechanism.getsLen());
signature = this.pkcs11Instance.C_SignFinal(this.session, mechanism.getSignFinalArgument());
}
else {
String errorMessage = "Unable to find keys.";
throw new Exception(errorMessage);
}
}
else {
throw new Exception("Initialize middleware first.");
}
}
/**
* #return
*/
private String getSignatureAlgorithmType() {
return this.signatureAlgo;
}
public void login(String userName, String password) throws Exception {
if (this.pkcs11Instance != null) {
openSession();
String pwd = password;
if (pwd == null || pwd.trim().isEmpty()) {
Scanner sc = new Scanner(System.in);
pwd = sc.next();
sc.close();
}
this.pkcs11Instance.C_Login(this.session, PKCS11Constants.CKU_USER, pwd.toCharArray());
this.isLoginDone = true;
}
else {
throw new Exception("Initialize middleware first.");
}
}
public void logout() throws PKCS11Exception {
if (this.pkcs11Instance != null) {
this.pkcs11Instance.C_Logout(this.session);
}
}
public void openSession() throws Exception {
if (this.pkcs11Instance != null) {
if (this.session < 0) {
long[] c_GetSlotList = this.pkcs11Instance.C_GetSlotList(true);
if ((c_GetSlotList != null) && (c_GetSlotList.length > 0)) {
for (long element : c_GetSlotList) {
if (element == this.slotId) {
this.session =
this.pkcs11Instance.C_OpenSession(this.slotId, PKCS11Constants.CKF_SERIAL_SESSION, null, null);
break;
}
}
}
}
}
else {
throw new Exception("Initialize middleware first.");
}
}
public void closeSession() throws PKCS11Exception {
if ((this.pkcs11Instance != null) && (this.session >= 0)) {
this.pkcs11Instance.C_CloseSession(this.session);
this.session = -1;
}
}
public void initializeMiddleware(String libraryPath1) throws IOException, PKCS11Exception {
this.pkcs11Instance = PKCS11.getInstance(libraryPath1, "C_GetFunctionList", null, false);
this.libraryPath = libraryPath1;
}
public void initializeMiddleware() throws IOException, PKCS11Exception {
this.pkcs11Instance = PKCS11.getInstance(this.libraryPath, "C_GetFunctionList", null, false);
}
public static String getString(final byte[] data) {
if ((data != null) && (data.length > 0)) {
return hexBinaryAdapter.marshal(data);
}
return null;
}
}
class SignatureMechanism {
public static enum Algorithms {
SHA256(0x250),
/**
* Hash calculation Algorithm
*/
RIPEMD160(0x240),
/**
* Hash calculation Algorithm
*/
RIPEMD160_1(0x0),
/**
* Secure Hash Algorithm 512 for hash Calculation
*/
SHA512(0x251);
private int value = 0;
private Algorithms(final int algorithmValue) {
this.value = algorithmValue;
}
/**
* #return the hash value
*/
public int getValue() {
return this.value;
}
}
/**
* #param algorithm : Algorithm used for hash calculation
* #return signature mechanism
*/
public static Mechanism getMechanism(final Algorithms algorithm) {
Mechanism mechanism = new Mechanism();
if (algorithm == Algorithms.SHA256) {
mechanism.setHashAlg(PKCS11Constants.CKM_SHA256);
mechanism.setMgf(PKCS11Constants.CKG_MGF1_SHA1 + 1);
mechanism.setsLen(32);
mechanism.setSignFinalArgument(512);
} // TODO Verify with ETAS Middleware team
else if (algorithm == Algorithms.SHA512) {
mechanism.setHashAlg(PKCS11Constants.CKM_SHA512);
mechanism.setMgf(PKCS11Constants.CKG_MGF1_SHA1 + 1);
mechanism.setsLen(64);
mechanism.setSignFinalArgument(1024);
}
else if (algorithm == Algorithms.RIPEMD160) {
mechanism.setHashAlg(PKCS11Constants.CKM_RIPEMD160);
mechanism.setMgf(0x80000001); // hard coded becuase it is defined by escrypt and not present in PKCS11
mechanism.setsLen(20);
}
else if (algorithm == Algorithms.RIPEMD160_1) {
mechanism.setId(PKCS11Constants.CKM_RIPEMD160_RSA_PKCS);
}
return mechanism;
}
}
class Mechanism{
private long hashAlg;
private long mgf;
private long sLen;
private long ulMaxObjectCount = 32;
private int signFinalArgument = 128;
private long id = PKCS11Constants.CKM_RSA_PKCS_PSS;
/**
* #return the hashAlg
*/
public long getHashAlg() {
return hashAlg;
}
/**
* #param hashAlg the hashAlg to set
*/
public void setHashAlg(long hashAlg) {
this.hashAlg = hashAlg;
}
/**
* #return the mgf
*/
public long getMgf() {
return mgf;
}
/**
* #param mgf the mgf to set
*/
public void setMgf(long mgf) {
this.mgf = mgf;
}
/**
* #return the sLen
*/
public long getsLen() {
return sLen;
}
/**
* #param sLen the sLen to set
*/
public void setsLen(long sLen) {
this.sLen = sLen;
}
/**
* #return the ulMaxObjectCount
*/
public long getUlMaxObjectCount() {
return ulMaxObjectCount;
}
/**
* #param ulMaxObjectCount the ulMaxObjectCount to set
*/
public void setUlMaxObjectCount(long ulMaxObjectCount) {
this.ulMaxObjectCount = ulMaxObjectCount;
}
/**
* #return the signFinalArgument
*/
public int getSignFinalArgument() {
return signFinalArgument;
}
/**
* #param signFinalArgument the signFinalArgument to set
*/
public void setSignFinalArgument(int signFinalArgument) {
this.signFinalArgument = signFinalArgument;
}
/**
* #return the id
*/
public long getId() {
return id;
}
/**
* #param id the id to set
*/
public void setId(long id) {
this.id = id;
}
}
'''
Thank You!
Java 9 up no longer puts the standard-library in jar files (rt.jar, etc) but they are still there. Your problem is probably (though I didn't read through all the code and certainly can't test without your hardware) that Java 9 up also introduces modules. Much as in Java forever your code can reference another class or interface (or static member therof) by its simple name only if you import it or it is in the same package as your code (if any) or the special package java.lang, now in 9 up you can only access a package if it is in the same module as your code or the special java.base module or your module and/or the other module explicitly allow the access; the explicit specification closet to older Java is an 'open'.
Thus the quick solution is to add to your java command (explicitly or via _JAVA_OPTS or equivalent) --add-opens jdk.crypto.cryptoki/sun.security.pkcs11.*=ALL-UNNAMED -- this should produce substantially the same result as running on 8 (or lower).
The official solution is to put your code in a module -- either by itself or with other related code -- that requires the (or each) needed module, given it exports the needed parts, which I haven't checked for yours. However, since your code apparently isn't even in a package yet, putting it in a module will be some work.
I'm working on an Android application that needs to crypt (and then to decrypt) file on the file system. I wrote an android test to test the code that I found on the web and I adapted for my needed. I try with to crypt a simple text and then try to decrypt it. The problem is when I try to decrypt it, some strange character appears at the beginning of the content that I want to crypt/decrypt. For example, I try to crypt/decrypt a string like this:
Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)
And I received
X��YK�P���$BProgramming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)
The test code is
#Test
public void test() throws IOException, GeneralSecurityException {
String input = "Concentration - Programming Music 0100 (Part 4)";
for (int i=0;i<10;i++) {
input+=input;
}
String password = EncryptSystem.encrypt(new ByteArrayInputStream(input.getBytes(Charset.forName("UTF-8"))), new File(this.context.getFilesDir(), "test.txt"));
InputStream inputStream = EncryptSystem.decrypt(password, new File(this.context.getFilesDir(), "test.txt"));
//creating an InputStreamReader object
InputStreamReader isReader = new InputStreamReader(inputStream, Charset.forName("UTF-8"));
//Creating a BufferedReader object
BufferedReader reader = new BufferedReader(isReader);
StringBuilder sb = new StringBuilder();
String str;
while ((str = reader.readLine()) != null) {
sb.append(str);
}
System.out.println(sb.toString());
Assert.assertEquals(input, sb.toString());
}
The class code is:
import android.os.Build;
import android.os.Process;
import android.util.Base64;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.crypto.*;
public class EncryptSystem {
public static class SecretKeys {
private SecretKey confidentialityKey;
private byte[] iv;
/**
* An aes key derived from a base64 encoded key. This does not generate the
* key. It's not random or a PBE key.
*
* #param keysStr a base64 encoded AES key / hmac key as base64(aesKey) : base64(hmacKey).
* #return an AES and HMAC key set suitable for other functions.
*/
public static SecretKeys of(String keysStr) throws InvalidKeyException {
String[] keysArr = keysStr.split(":");
if (keysArr.length != 2) {
throw new IllegalArgumentException("Cannot parse aesKey:iv");
} else {
byte[] confidentialityKey = Base64.decode(keysArr[0], BASE64_FLAGS);
if (confidentialityKey.length != AES_KEY_LENGTH_BITS / 8) {
throw new InvalidKeyException("Base64 decoded key is not " + AES_KEY_LENGTH_BITS + " bytes");
}
byte[] iv = Base64.decode(keysArr[1], BASE64_FLAGS);
/* if (iv.length != HMAC_KEY_LENGTH_BITS / 8) {
throw new InvalidKeyException("Base64 decoded key is not " + HMAC_KEY_LENGTH_BITS + " bytes");
}*/
return new SecretKeys(
new SecretKeySpec(confidentialityKey, 0, confidentialityKey.length, CIPHER),
iv);
}
}
public SecretKeys(SecretKey confidentialityKeyIn, byte[] i) {
setConfidentialityKey(confidentialityKeyIn);
iv = new byte[i.length];
System.arraycopy(i, 0, iv, 0, i.length);
}
public SecretKey getConfidentialityKey() {
return confidentialityKey;
}
public void setConfidentialityKey(SecretKey confidentialityKey) {
this.confidentialityKey = confidentialityKey;
}
#Override
public String toString() {
return Base64.encodeToString(getConfidentialityKey().getEncoded(), BASE64_FLAGS)
+ ":" + Base64.encodeToString(this.iv, BASE64_FLAGS);
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SecretKeys that = (SecretKeys) o;
return confidentialityKey.equals(that.confidentialityKey) &&
Arrays.equals(iv, that.iv);
}
#Override
public int hashCode() {
int result = Objects.hash(confidentialityKey);
result = 31 * result + Arrays.hashCode(iv);
return result;
}
public byte[] getIv() {
return this.iv;
}
}
// If the PRNG fix would not succeed for some reason, we normally will throw an exception.
// If ALLOW_BROKEN_PRNG is true, however, we will simply log instead.
private static final boolean ALLOW_BROKEN_PRNG = false;
private static final String CIPHER_TRANSFORMATION = "AES/CBC/PKCS5Padding";
private static final String CIPHER = "AES";
private static final int AES_KEY_LENGTH_BITS = 128;
private static final int IV_LENGTH_BYTES = 16;
private static final int PBE_ITERATION_COUNT = 10000;
private static final int PBE_SALT_LENGTH_BITS = AES_KEY_LENGTH_BITS; // same size as key output
private static final String PBE_ALGORITHM = "PBKDF2WithHmacSHA1";
//Made BASE_64_FLAGS public as it's useful to know for compatibility.
public static final int BASE64_FLAGS = Base64.NO_WRAP;
//default for testing
static final AtomicBoolean prngFixed = new AtomicBoolean(false);
private static final String HMAC_ALGORITHM = "HmacSHA256";
private static final int HMAC_KEY_LENGTH_BITS = 256;
public static SecretKeys generateKey() throws GeneralSecurityException {
fixPrng();
KeyGenerator keyGen = KeyGenerator.getInstance(CIPHER);
// No need to provide a SecureRandom or set a seed since that will
// happen automatically.
keyGen.init(AES_KEY_LENGTH_BITS);
SecretKey confidentialityKey = keyGen.generateKey();
return new SecretKeys(confidentialityKey, generateIv());
}
private static void fixPrng() {
if (!prngFixed.get()) {
synchronized (PrngFixes.class) {
if (!prngFixed.get()) {
PrngFixes.apply();
prngFixed.set(true);
}
}
}
}
private static byte[] randomBytes(int length) throws GeneralSecurityException {
fixPrng();
SecureRandom random = new SecureRandom();
byte[] b = new byte[length];
random.nextBytes(b);
return b;
}
private static byte[] generateIv() throws GeneralSecurityException {
return randomBytes(IV_LENGTH_BYTES);
}
private static String keyString(SecretKeys keys) {
return keys.toString();
}
public static SecretKeys generateKeyFromPassword(String password, byte[] salt) throws GeneralSecurityException {
fixPrng();
//Get enough random bytes for both the AES key and the HMAC key:
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt,
PBE_ITERATION_COUNT, AES_KEY_LENGTH_BITS + HMAC_KEY_LENGTH_BITS);
SecretKeyFactory keyFactory = SecretKeyFactory
.getInstance(PBE_ALGORITHM);
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
// Split the random bytes into two parts:
byte[] confidentialityKeyBytes = copyOfRange(keyBytes, 0, AES_KEY_LENGTH_BITS / 8);
byte[] integrityKeyBytes = copyOfRange(keyBytes, AES_KEY_LENGTH_BITS / 8, AES_KEY_LENGTH_BITS / 8 + HMAC_KEY_LENGTH_BITS / 8);
//Generate the AES key
SecretKey confidentialityKey = new SecretKeySpec(confidentialityKeyBytes, CIPHER);
return new SecretKeys(confidentialityKey, generateIv());
}
private static byte[] copyOfRange(byte[] from, int start, int end) {
int length = end - start;
byte[] result = new byte[length];
System.arraycopy(from, start, result, 0, length);
return result;
}
public static SecretKeys generateKeyFromPassword(String password, String salt) throws GeneralSecurityException {
return generateKeyFromPassword(password, Base64.decode(salt, BASE64_FLAGS));
}
public static String encrypt(InputStream inputStream, File fileToWrite)
throws GeneralSecurityException {
SecretKeys secretKeys = generateKey();
return encrypt(inputStream, secretKeys, fileToWrite);
}
public static InputStream decrypt(String secretKey, File fileToRead) throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, FileNotFoundException {
SecretKeys secretKeys = SecretKeys.of(secretKey);
Cipher aesCipherForDecryption = Cipher.getInstance(CIPHER_TRANSFORMATION);
aesCipherForDecryption.init(Cipher.DECRYPT_MODE, secretKeys.getConfidentialityKey(),
new IvParameterSpec(secretKeys.getIv()));
return new CipherInputStream(new FileInputStream(fileToRead), aesCipherForDecryption);
}
private static String encrypt(InputStream inputStream, SecretKeys secretKeys, File fileToWrite)
throws GeneralSecurityException {
byte[] iv = generateIv();
Cipher aesCipherForEncryption = Cipher.getInstance(CIPHER_TRANSFORMATION);
aesCipherForEncryption.init(Cipher.ENCRYPT_MODE, secretKeys.getConfidentialityKey(), new IvParameterSpec(iv));
saveFile(inputStream, aesCipherForEncryption, fileToWrite);
/*
* Now we get back the IV that will actually be used. Some Android
* versions do funny stuff w/ the IV, so this is to work around bugs:
*/
/*iv = aesCipherForEncryption.getIV();
//byte[] byteCipherText = aesCipherForEncryption.doFinal(plaintext);
byte[] ivCipherConcat = CipherTextIvMac.ivCipherConcat(iv, byteCipherText);
byte[] integrityMac = generateMac(ivCipherConcat, secretKeys.getIntegrityKey());
return new CipherTextIvMac(byteCipherText, iv, integrityMac);*/
return secretKeys.toString();
}
private static boolean saveFile(InputStream inputStream, Cipher aesCipherForEncryption, File fileToWrite) {
try {
OutputStream outputStream = null;
try {
byte[] fileReader = new byte[4096];
/*long fileSize = body.contentLength();*/
long fileSizeDownloaded = 0;
outputStream = new CipherOutputStream(new FileOutputStream(fileToWrite), aesCipherForEncryption);
while (true) {
int read = inputStream.read(fileReader);
if (read == -1) {
break;
}
outputStream.write(fileReader, 0, read);
fileSizeDownloaded += read;
}
outputStream.flush();
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
} catch (IOException e) {
return false;
}
}
public static final class PrngFixes {
private static final int VERSION_CODE_JELLY_BEAN = 16;
private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = getBuildFingerprintAndDeviceSerial();
/**
* Hidden constructor to prevent instantiation.
*/
private PrngFixes() {
}
/**
* Applies all fixes.
*
* #throws SecurityException if a fix is needed but could not be
* applied.
*/
public static void apply() {
applyOpenSSLFix();
installLinuxPRNGSecureRandom();
}
/**
* Applies the fix for OpenSSL PRNG having low entropy. Does nothing if
* the fix is not needed.
*
* #throws SecurityException if the fix is needed but could not be
* applied.
*/
private static void applyOpenSSLFix() throws SecurityException {
if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN)
|| (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) {
// No need to apply the fix
return;
}
try {
// Mix in the device- and invocation-specific seed.
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_seed", byte[].class).invoke(null, generateSeed());
// Mix output of Linux PRNG into OpenSSL's PRNG
int bytesRead = (Integer) Class
.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_load_file", String.class, long.class)
.invoke(null, "/dev/urandom", 1024);
if (bytesRead != 1024) {
throw new IOException("Unexpected number of bytes read from Linux PRNG: "
+ bytesRead);
}
} catch (Exception e) {
if (ALLOW_BROKEN_PRNG) {
Log.w(PrngFixes.class.getSimpleName(), "Failed to seed OpenSSL PRNG", e);
} else {
throw new SecurityException("Failed to seed OpenSSL PRNG", e);
}
}
}
/**
* Installs a Linux PRNG-backed {#code SecureRandom} implementation as
* the default. Does nothing if the implementation is already the
* default or if there is not need to install the implementation.
*
* #throws SecurityException if the fix is needed but could not be
* applied.
*/
private static void installLinuxPRNGSecureRandom() throws SecurityException {
if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
// No need to apply the fix
return;
}
// Install a Linux PRNG-based SecureRandom implementation as the
// default, if not yet installed.
Provider[] secureRandomProviders = Security.getProviders("SecureRandom.SHA1PRNG");
// Insert and check the provider atomically.
// The official Android Java libraries use synchronized methods for
// insertProviderAt, etc., so synchronizing on the class should
// make things more stable, and prevent race conditions with other
// versions of this code.
synchronized (java.security.Security.class) {
if ((secureRandomProviders == null)
|| (secureRandomProviders.length < 1)
|| (!secureRandomProviders[0].getClass().getSimpleName().equals("LinuxPRNGSecureRandomProvider"))) {
Security.insertProviderAt(new PrngFixes.LinuxPRNGSecureRandomProvider(), 1);
}
// Assert that new SecureRandom() and
// SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
// by the Linux PRNG-based SecureRandom implementation.
SecureRandom rng1 = new SecureRandom();
if (!rng1.getProvider().getClass().getSimpleName().equals("LinuxPRNGSecureRandomProvider")) {
if (ALLOW_BROKEN_PRNG) {
Log.w(PrngFixes.class.getSimpleName(),
"new SecureRandom() backed by wrong Provider: " + rng1.getProvider().getClass());
return;
} else {
throw new SecurityException("new SecureRandom() backed by wrong Provider: "
+ rng1.getProvider().getClass());
}
}
SecureRandom rng2 = null;
try {
rng2 = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
if (ALLOW_BROKEN_PRNG) {
Log.w(PrngFixes.class.getSimpleName(), "SHA1PRNG not available", e);
return;
} else {
new SecurityException("SHA1PRNG not available", e);
}
}
if (!rng2.getProvider().getClass().getSimpleName().equals("LinuxPRNGSecureRandomProvider")) {
if (ALLOW_BROKEN_PRNG) {
Log.w(PrngFixes.class.getSimpleName(),
"SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" + " Provider: "
+ rng2.getProvider().getClass());
return;
} else {
throw new SecurityException(
"SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" + " Provider: "
+ rng2.getProvider().getClass());
}
}
}
}
/**
* {#code Provider} of {#code SecureRandom} engines which pass through
* all requests to the Linux PRNG.
*/
private static class LinuxPRNGSecureRandomProvider extends Provider {
public LinuxPRNGSecureRandomProvider() {
super("LinuxPRNG", 1.0, "A Linux-specific random number provider that uses"
+ " /dev/urandom");
// Although /dev/urandom is not a SHA-1 PRNG, some apps
// explicitly request a SHA1PRNG SecureRandom and we thus need
// to prevent them from getting the default implementation whose
// output may have low entropy.
put("SecureRandom.SHA1PRNG", PrngFixes.LinuxPRNGSecureRandom.class.getName());
put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
}
}
/**
* {#link SecureRandomSpi} which passes all requests to the Linux PRNG (
* {#code /dev/urandom}).
*/
public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
/*
* IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a
* seed are passed through to the Linux PRNG (/dev/urandom).
* Instances of this class seed themselves by mixing in the current
* time, PID, UID, build fingerprint, and hardware serial number
* (where available) into Linux PRNG.
*
* Concurrency: Read requests to the underlying Linux PRNG are
* serialized (on sLock) to ensure that multiple threads do not get
* duplicated PRNG output.
*/
private static final File URANDOM_FILE = new File("/dev/urandom");
private static final Object sLock = new Object();
/**
* Input stream for reading from Linux PRNG or {#code null} if not
* yet opened.
*
* #GuardedBy("sLock")
*/
private static DataInputStream sUrandomIn;
/**
* Output stream for writing to Linux PRNG or {#code null} if not
* yet opened.
*
* #GuardedBy("sLock")
*/
private static OutputStream sUrandomOut;
/**
* Whether this engine instance has been seeded. This is needed
* because each instance needs to seed itself if the client does not
* explicitly seed it.
*/
private boolean mSeeded;
#Override
protected void engineSetSeed(byte[] bytes) {
try {
OutputStream out;
synchronized (sLock) {
out = getUrandomOutputStream();
}
out.write(bytes);
out.flush();
} catch (IOException e) {
// On a small fraction of devices /dev/urandom is not
// writable Log and ignore.
Log.w(PrngFixes.class.getSimpleName(), "Failed to mix seed into "
+ URANDOM_FILE);
} finally {
mSeeded = true;
}
}
#Override
protected void engineNextBytes(byte[] bytes) {
if (!mSeeded) {
// Mix in the device- and invocation-specific seed.
engineSetSeed(generateSeed());
}
try {
DataInputStream in;
synchronized (sLock) {
in = getUrandomInputStream();
}
synchronized (in) {
in.readFully(bytes);
}
} catch (IOException e) {
throw new SecurityException("Failed to read from " + URANDOM_FILE, e);
}
}
#Override
protected byte[] engineGenerateSeed(int size) {
byte[] seed = new byte[size];
engineNextBytes(seed);
return seed;
}
private DataInputStream getUrandomInputStream() {
synchronized (sLock) {
if (sUrandomIn == null) {
// NOTE: Consider inserting a BufferedInputStream
// between DataInputStream and FileInputStream if you need
// higher PRNG output performance and can live with future PRNG
// output being pulled into this process prematurely.
try {
sUrandomIn = new DataInputStream(new FileInputStream(URANDOM_FILE));
} catch (IOException e) {
throw new SecurityException("Failed to open " + URANDOM_FILE
+ " for reading", e);
}
}
return sUrandomIn;
}
}
private OutputStream getUrandomOutputStream() throws IOException {
synchronized (sLock) {
if (sUrandomOut == null) {
sUrandomOut = new FileOutputStream(URANDOM_FILE);
}
return sUrandomOut;
}
}
}
/**
* Generates a device- and invocation-specific seed to be mixed into the
* Linux PRNG.
*/
private static byte[] generateSeed() {
try {
ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
DataOutputStream seedBufferOut = new DataOutputStream(seedBuffer);
seedBufferOut.writeLong(System.currentTimeMillis());
seedBufferOut.writeLong(System.nanoTime());
seedBufferOut.writeInt(Process.myPid());
seedBufferOut.writeInt(Process.myUid());
seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
seedBufferOut.close();
return seedBuffer.toByteArray();
} catch (IOException e) {
throw new SecurityException("Failed to generate seed", e);
}
}
/**
* Gets the hardware serial number of this device.
*
* #return serial number or {#code null} if not available.
*/
private static String getDeviceSerialNumber() {
// We're using the Reflection API because of Build.SERIAL is only
// available since API Level 9 (Gingerbread, Android 2.3).
try {
return (String) Build.class.getField("SERIAL").get(null);
} catch (Exception ignored) {
return null;
}
}
private static byte[] getBuildFingerprintAndDeviceSerial() {
StringBuilder result = new StringBuilder();
String fingerprint = Build.FINGERPRINT;
if (fingerprint != null) {
result.append(fingerprint);
}
String serial = getDeviceSerialNumber();
if (serial != null) {
result.append(serial);
}
try {
return result.toString().getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 encoding not supported");
}
}
}
}
Any idea about what I'm in wrong? Thank you in advance
Finally, I solve by myself. I post the solution just to help anybody that in the future will look for a similar situation. I mistake to retrieve the iv array in the encrypt method, I was generating another iv vector instead of using the one contained in secretKeys.
private static String encrypt(InputStream inputStream, SecretKeys secretKeys, File fileToWrite)
throws GeneralSecurityException {
Cipher aesCipherForEncryption = Cipher.getInstance(CIPHER_TRANSFORMATION);
aesCipherForEncryption.init(Cipher.ENCRYPT_MODE, secretKeys.getConfidentialityKey(), new IvParameterSpec(secretKeys.getIv()));
saveFile(inputStream, aesCipherForEncryption, fileToWrite);
return secretKeys.toString();
}
I have two applications that interact using Thrift. They share the same secret key and I need to encrypt their messages. It makes sense to use symmetric algorithm (AES, for example), but I haven't found any library to do this. So I made a research and see following options:
Use built-in SSL support
I can use built-in SSL support, establish secure connection and use my secret key just as authentication token. It requires to install certificates in addition to the secret key they already have, but I don't need to implement anything except checking that secret key received from client is the same as secret key stored locally.
Implement symmetric encryption
So far, there are following options:
Extend TSocket and override write() and read() methods and en- / decrypt data in them. Will have increasing of traffic on small writes. For example, if TBinaryProtocol writes 4-bytes integer, it will take one block (16 bytes) in encrypted state.
Extend TSocket and wrap InputStream and OutputStream with CipherInputStream and CipherOutputStream. CipherOutputStream will not encrypt small byte arrays immediately, updating Cipher with them. After we have enough data, they will be encrypted and written to the underlying OutputStream. So it will wait until you add 4 4-byte ints and encrypt them then. It allows us not wasting traffic, but is also a cause of problem - if last value will not fill the block, it will be never encrypted and written to the underlying stream. It expects me to write number of bytes divisible by its block size (16 byte), but I can't do this using TBinaryProtocol.
Re-implement TBinaryProtocol, caching all writes instead of writing them to stream and encrypting in writeMessageEnd() method. Implement decryption in readMessageBegin(). I think encryption should be performed on the transport layer, not protocol one.
Please share your thoughts with me.
UPDATE
Java Implementation on Top of TFramedTransport
TEncryptedFramedTransport.java
package tutorial;
import org.apache.thrift.TByteArrayOutputStream;
import org.apache.thrift.transport.TMemoryInputTransport;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
import org.apache.thrift.transport.TTransportFactory;
import javax.crypto.Cipher;
import java.security.Key;
/**
* TEncryptedFramedTransport is a buffered TTransport. It encrypts fully read message
* with the "AES/ECB/PKCS5Padding" symmetric algorithm and send it, preceeding with a 4-byte frame size.
*/
public class TEncryptedFramedTransport extends TTransport {
public static final String ALGORITHM = "AES/ECB/PKCS5Padding";
private Cipher encryptingCipher;
private Cipher decryptingCipher;
protected static final int DEFAULT_MAX_LENGTH = 0x7FFFFFFF;
private int maxLength_;
private TTransport transport_ = null;
private final TByteArrayOutputStream writeBuffer_ = new TByteArrayOutputStream(1024);
private TMemoryInputTransport readBuffer_ = new TMemoryInputTransport(new byte[0]);
public static class Factory extends TTransportFactory {
private int maxLength_;
private Key secretKey_;
public Factory(Key secretKey) {
this(secretKey, DEFAULT_MAX_LENGTH);
}
public Factory(Key secretKey, int maxLength) {
maxLength_ = maxLength;
secretKey_ = secretKey;
}
#Override
public TTransport getTransport(TTransport base) {
return new TEncryptedFramedTransport(base, secretKey_, maxLength_);
}
}
/**
* Constructor wraps around another tranpsort
*/
public TEncryptedFramedTransport(TTransport transport, Key secretKey, int maxLength) {
transport_ = transport;
maxLength_ = maxLength;
try {
encryptingCipher = Cipher.getInstance(ALGORITHM);
encryptingCipher.init(Cipher.ENCRYPT_MODE, secretKey);
decryptingCipher = Cipher.getInstance(ALGORITHM);
decryptingCipher.init(Cipher.DECRYPT_MODE, secretKey);
} catch (Exception e) {
throw new RuntimeException("Unable to initialize ciphers.");
}
}
public TEncryptedFramedTransport(TTransport transport, Key secretKey) {
this(transport, secretKey, DEFAULT_MAX_LENGTH);
}
public void open() throws TTransportException {
transport_.open();
}
public boolean isOpen() {
return transport_.isOpen();
}
public void close() {
transport_.close();
}
public int read(byte[] buf, int off, int len) throws TTransportException {
if (readBuffer_ != null) {
int got = readBuffer_.read(buf, off, len);
if (got > 0) {
return got;
}
}
// Read another frame of data
readFrame();
return readBuffer_.read(buf, off, len);
}
#Override
public byte[] getBuffer() {
return readBuffer_.getBuffer();
}
#Override
public int getBufferPosition() {
return readBuffer_.getBufferPosition();
}
#Override
public int getBytesRemainingInBuffer() {
return readBuffer_.getBytesRemainingInBuffer();
}
#Override
public void consumeBuffer(int len) {
readBuffer_.consumeBuffer(len);
}
private final byte[] i32buf = new byte[4];
private void readFrame() throws TTransportException {
transport_.readAll(i32buf, 0, 4);
int size = decodeFrameSize(i32buf);
if (size < 0) {
throw new TTransportException("Read a negative frame size (" + size + ")!");
}
if (size > maxLength_) {
throw new TTransportException("Frame size (" + size + ") larger than max length (" + maxLength_ + ")!");
}
byte[] buff = new byte[size];
transport_.readAll(buff, 0, size);
try {
buff = decryptingCipher.doFinal(buff);
} catch (Exception e) {
throw new TTransportException(0, e);
}
readBuffer_.reset(buff);
}
public void write(byte[] buf, int off, int len) throws TTransportException {
writeBuffer_.write(buf, off, len);
}
#Override
public void flush() throws TTransportException {
byte[] buf = writeBuffer_.get();
int len = writeBuffer_.len();
writeBuffer_.reset();
try {
buf = encryptingCipher.doFinal(buf, 0, len);
} catch (Exception e) {
throw new TTransportException(0, e);
}
encodeFrameSize(buf.length, i32buf);
transport_.write(i32buf, 0, 4);
transport_.write(buf);
transport_.flush();
}
public static void encodeFrameSize(final int frameSize, final byte[] buf) {
buf[0] = (byte) (0xff & (frameSize >> 24));
buf[1] = (byte) (0xff & (frameSize >> 16));
buf[2] = (byte) (0xff & (frameSize >> 8));
buf[3] = (byte) (0xff & (frameSize));
}
public static int decodeFrameSize(final byte[] buf) {
return
((buf[0] & 0xff) << 24) |
((buf[1] & 0xff) << 16) |
((buf[2] & 0xff) << 8) |
((buf[3] & 0xff));
}
}
MultiplicationServer.java
package tutorial;
import co.runit.prototype.CryptoTool;
import org.apache.thrift.server.TNonblockingServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TNonblockingServerSocket;
import org.apache.thrift.transport.TNonblockingServerTransport;
import java.security.Key;
public class MultiplicationServer {
public static MultiplicationHandler handler;
public static MultiplicationService.Processor processor;
public static void main(String[] args) {
try {
handler = new MultiplicationHandler();
processor = new MultiplicationService.Processor(handler);
Runnable simple = () -> startServer(processor);
new Thread(simple).start();
} catch (Exception x) {
x.printStackTrace();
}
}
public static void startServer(MultiplicationService.Processor processor) {
try {
Key key = CryptoTool.decodeKeyBase64("1OUXS3MczVFp3SdfX41U0A==");
TNonblockingServerTransport serverTransport = new TNonblockingServerSocket(9090);
TServer server = new TNonblockingServer(new TNonblockingServer.Args(serverTransport)
.transportFactory(new TEncryptedFramedTransport.Factory(key))
.processor(processor));
System.out.println("Starting the simple server...");
server.serve();
} catch (Exception e) {
e.printStackTrace();
}
}
}
MultiplicationClient.java
package tutorial;
import co.runit.prototype.CryptoTool;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import java.security.Key;
public class MultiplicationClient {
public static void main(String[] args) {
Key key = CryptoTool.decodeKeyBase64("1OUXS3MczVFp3SdfX41U0A==");
try {
TSocket baseTransport = new TSocket("localhost", 9090);
TTransport transport = new TEncryptedFramedTransport(baseTransport, key);
transport.open();
TProtocol protocol = new TBinaryProtocol(transport);
MultiplicationService.Client client = new MultiplicationService.Client(protocol);
perform(client);
transport.close();
} catch (TException x) {
x.printStackTrace();
}
}
private static void perform(MultiplicationService.Client client) throws TException {
int product = client.multiply(3, 5);
System.out.println("3*5=" + product);
}
}
Of course, keys must be the same on the client and server. To generate and store it in Base64:
public static String generateKey() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
KeyGenerator generator = KeyGenerator.getInstance("AES");
generator.init(128);
Key key = generator.generateKey();
return encodeKeyBase64(key);
}
public static String encodeKeyBase64(Key key) {
return Base64.getEncoder().encodeToString(key.getEncoded());
}
public static Key decodeKeyBase64(String encodedKey) {
byte[] keyBytes = Base64.getDecoder().decode(encodedKey);
return new SecretKeySpec(keyBytes, ALGORITHM);
}
UPDATE 2
Python Implementation on Top of TFramedTransport
TEncryptedTransport.py
from cStringIO import StringIO
from struct import pack, unpack
from Crypto.Cipher import AES
from thrift.transport.TTransport import TTransportBase, CReadableTransport
__author__ = 'Marboni'
BLOCK_SIZE = 16
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * chr(BLOCK_SIZE - len(s) % BLOCK_SIZE)
unpad = lambda s: '' if not s else s[0:-ord(s[-1])]
class TEncryptedFramedTransportFactory:
def __init__(self, key):
self.__key = key
def getTransport(self, trans):
return TEncryptedFramedTransport(trans, self.__key)
class TEncryptedFramedTransport(TTransportBase, CReadableTransport):
def __init__(self, trans, key):
self.__trans = trans
self.__rbuf = StringIO()
self.__wbuf = StringIO()
self.__cipher = AES.new(key)
def isOpen(self):
return self.__trans.isOpen()
def open(self):
return self.__trans.open()
def close(self):
return self.__trans.close()
def read(self, sz):
ret = self.__rbuf.read(sz)
if len(ret) != 0:
return ret
self.readFrame()
return self.__rbuf.read(sz)
def readFrame(self):
buff = self.__trans.readAll(4)
sz, = unpack('!i', buff)
encrypted = StringIO(self.__trans.readAll(sz)).getvalue()
decrypted = unpad(self.__cipher.decrypt(encrypted))
self.__rbuf = StringIO(decrypted)
def write(self, buf):
self.__wbuf.write(buf)
def flush(self):
wout = self.__wbuf.getvalue()
self.__wbuf = StringIO()
encrypted = self.__cipher.encrypt(pad(wout))
encrypted_len = len(encrypted)
buf = pack("!i", encrypted_len) + encrypted
self.__trans.write(buf)
self.__trans.flush()
# Implement the CReadableTransport interface.
#property
def cstringio_buf(self):
return self.__rbuf
def cstringio_refill(self, prefix, reqlen):
while len(prefix) < reqlen:
self.readFrame()
prefix += self.__rbuf.getvalue()
self.__rbuf = StringIO(prefix)
return self.__rbuf
MultiplicationClient.py
import base64
from thrift import Thrift
from thrift.transport import TSocket
from thrift.protocol import TBinaryProtocol
from tutorial import MultiplicationService, TEncryptedTransport
key = base64.b64decode("1OUXS3MczVFp3SdfX41U0A==")
try:
transport = TSocket.TSocket('localhost', 9090)
transport = TEncryptedTransport.TEncryptedFramedTransport(transport, key)
protocol = TBinaryProtocol.TBinaryProtocol(transport)
client = MultiplicationService.Client(protocol)
transport.open()
product = client.multiply(4, 5, 'Echo!')
print '4*5=%d' % product
transport.close()
except Thrift.TException, tx:
print tx.message
As stated by JensG, sending an externally encrypted binary or supplying a layered cipher transport are the two best options. It you need a template, take a look at the TFramedTransport. It is a simple layered transport and could easily be used as a starting block for creating a TCipherTransport.
The following code is being used to upload three JPG-Image files using FTP in JAVA.
The files are uploaded and confirmed with a "successful"-message, but the files are corrupted. The small tiny thumbnails-files are partly readable (top quarter).
I tried searching the net and added
setFileType(FTPClient.BINARY_FILE_TYPE);
But the problem still occurs :(
Could somebody have a look at it and give me a hint or advice ?
Code:
package de.immozukunft.programs;
import java.io.*;
import java.util.Locale;
import java.util.ResourceBundle;
import org.apache.commons.net.ftp.FTPClient;
/**
* This class enables the ability to connect and trasfer data to the FTP server
*/
public class FtpUpDown {
static Locale locale = new Locale("de"); // Locale is set to "de" for
// Germany
static ResourceBundle r = ResourceBundle.getBundle("Strings", locale); // ResourceBundle
// for
// different
// languages
// and
// String
// Management
// FTP-Connection properties
static String host = "IP-address"; //Host
static String username = "username"; // Username
static int port = 21; //Port
static String password = "password"; // Password
/**
* <h3>FTP-connection tester</h3>
*
*/
public static boolean connect() {
FTPClient ftpClient = new FTPClient();
try {
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
ftpClient.connect(host, port);
ftpClient.login(username, password);
ftpClient.logout();
ftpClient.disconnect();
} catch (Exception e) {
// e.printStackTrace();
System.err.println("Unable to connect"); // TODO String einfügen
return (false);
}
System.out.println("Connection established"); // TODO String einfügen
return (true);
}
/**
* <h3>FTP-Status</h3>
*
* #return
* #throws IOException
*/
static public String getStatus() {
if (connect()) {
return (r.getString("successConnectFTP"));
} else {
return (r.getString("unableToConnectFTP"));
}
}
/**
* <h3>FTP-filelist</h3>
*
* #return String-Array der Dateinamen auf dem FTP-Server
*/
public static String[] list() throws IOException {
FTPClient ftpClient = new FTPClient();
String[] filenameList;
try {
ftpClient.connect(host, port);
ftpClient.login(username, password);
filenameList = ftpClient.listNames();
ftpClient.logout();
} finally {
ftpClient.disconnect();
}
return filenameList;
}
/**
* <h3>FTP-Client-Download:</h3>
*
* #return true falls ok
*/
public static boolean download(String localResultFile,
String remoteSourceFile, boolean showMessages) throws IOException {
FTPClient ftpClient = new FTPClient();
FileOutputStream fos = null;
boolean resultOk = true;
try {
ftpClient.connect(host, port);
if (showMessages) {
System.out.println(ftpClient.getReplyString());
}
resultOk &= ftpClient.login(username, password);
if (showMessages) {
System.out.println(ftpClient.getReplyString());
}
fos = new FileOutputStream(localResultFile);
resultOk &= ftpClient.retrieveFile(remoteSourceFile, fos);
if (showMessages) {
System.out.println(ftpClient.getReplyString());
}
resultOk &= ftpClient.logout();
if (showMessages) {
System.out.println(ftpClient.getReplyString());
}
} finally {
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {/* nothing to do */
}
ftpClient.disconnect();
}
return resultOk;
}
/**
* <h3>FTP-Client-Upload:</h3>
*
* #param localSourceFile
* The source of local file
* #param remoteResultFile
* Set the destination of the file
* #param showMessages
* If set on TRUE messages will be displayed on the console
* #return true Returns If successfully transfered it will return TRUE, else
* FALSE
*/
public static boolean upload(String localSourceFile,
String remoteResultFile, boolean showMessages) throws IOException {
FTPClient ftpClient = new FTPClient();
FileInputStream fis = null;
boolean resultOk = true;
try {
ftpClient.connect(host, port);
if (showMessages) {
System.out.println(ftpClient.getReplyString());
}
resultOk &= ftpClient.login(username, password);
if (showMessages) {
System.out.println(ftpClient.getReplyString());
}
fis = new FileInputStream(localSourceFile);
resultOk &= ftpClient.storeFile(remoteResultFile, fis);
if (showMessages) {
System.out.println(ftpClient.getReplyString());
}
resultOk &= ftpClient.logout();
if (showMessages) {
System.out.println(ftpClient.getReplyString());
}
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {/* nothing to do */
}
ftpClient.disconnect();
}
return resultOk;
}
// Setter and Getter-methods
public static String getHost() {
return host;
}
public static void setHost(String host) {
FtpUpDown.host = host;
}
public static String getUsername() {
return username;
}
public static void setUsername(String username) {
FtpUpDown.username = username;
}
public static int getPort() {
return port;
}
public static void setPort(int port) {
FtpUpDown.port = port;
}
public static String getPassword() {
return password;
}
public static void setPassword(String password) {
FtpUpDown.password = password;
}
}
greets
THE-E
So I finally figured out what the problem was.
The problem was caused by the order of setFileType(FTPClient.BINARY_FILE_TYPE). It needs to be positioned in the upload()-method after the fis = new FileInputStream(localSourceFile) and before the ftpClient.storeFile(remoteResultFile, fis)
So the full working code is:
import java.io.*;
import java.util.Locale;
import java.util.ResourceBundle;
import org.apache.commons.net.ftp.FTPClient;
/**
* This class enables the ability to connect and trasfer data to the FTP server
*/
public class FtpUpDown {
static Locale locale = new Locale("de"); // Locale is set to "de" for
// Germany
static ResourceBundle r = ResourceBundle.getBundle("Strings", locale); // ResourceBundle
// for
// different
// languages
// and
// String
// Management
// FTP-Connection properties
static String host = "IP-Address"; // IP-address
static String username = "username"; // Username
static int port = 21; // Port
static String password = "password"; // Password
/**
* <h3>FTP-connection tester</h3>
*
*/
public static boolean connect() {
FTPClient ftpClient = new FTPClient();
try {
ftpClient.connect(host, port);
ftpClient.login(username, password);
ftpClient.logout();
ftpClient.disconnect();
} catch (Exception e) {
// e.printStackTrace();
System.err.println("Unable to connect"); // TODO String einfügen
return (false);
}
System.out.println("Connection established"); // TODO String einfügen
return (true);
}
/**
* <h3>FTP-Status</h3>
*
* #return
* #throws IOException
*/
static public String getStatus() {
if (connect()) {
return (r.getString("successConnectFTP"));
} else {
return (r.getString("unableToConnectFTP"));
}
}
/**
* <h3>FTP-filelist</h3>
*
* #return String-Array der Dateinamen auf dem FTP-Server
*/
public static String[] list() throws IOException {
FTPClient ftpClient = new FTPClient();
String[] filenameList;
try {
ftpClient.connect(host, port);
ftpClient.login(username, password);
filenameList = ftpClient.listNames();
ftpClient.logout();
} finally {
ftpClient.disconnect();
}
return filenameList;
}
/**
* <h3>FTP-Client-Download:</h3>
*
* #return true falls ok
*/
public static boolean download(String localResultFile,
String remoteSourceFile, boolean showMessages) throws IOException {
FTPClient ftpClient = new FTPClient();
FileOutputStream fos = null;
boolean resultOk = true;
try {
ftpClient.connect(host, port);
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
if (showMessages) {
System.out.println(ftpClient.getReplyString());
}
resultOk &= ftpClient.login(username, password);
if (showMessages) {
System.out.println(ftpClient.getReplyString());
}
fos = new FileOutputStream(localResultFile);
resultOk &= ftpClient.retrieveFile(remoteSourceFile, fos);
if (showMessages) {
System.out.println(ftpClient.getReplyString());
}
resultOk &= ftpClient.logout();
if (showMessages) {
System.out.println(ftpClient.getReplyString());
}
} finally {
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {/* nothing to do */
}
ftpClient.disconnect();
}
return resultOk;
}
/**
* <h3>FTP-Client-Upload:</h3>
*
* #param localSourceFile
* The source of local file
* #param remoteResultFile
* Set the destination of the file
* #param showMessages
* If set on TRUE messages will be displayed on the console
* #return true Returns If successfully transfered it will return TRUE, else
* FALSE
*/
public static boolean upload(String localSourceFile,
String remoteResultFile, boolean showMessages) throws IOException {
FTPClient ftpClient = new FTPClient();
FileInputStream fis = null;
boolean resultOk = true;
try {
ftpClient.connect(host, port);
if (showMessages) {
System.out.println(ftpClient.getReplyString());
}
resultOk &= ftpClient.login(username, password);
if (showMessages) {
System.out.println(ftpClient.getReplyString());
}
fis = new FileInputStream(localSourceFile);
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
resultOk &= ftpClient.storeFile(remoteResultFile, fis);
if (showMessages) {
System.out.println(ftpClient.getReplyString());
}
resultOk &= ftpClient.logout();
if (showMessages) {
System.out.println(ftpClient.getReplyString());
}
} finally {
try {
if (fis != null) {
fis.close();
}
ftpClient.disconnect();
} catch (IOException e) {/* nothing to do */
}
}
return resultOk;
}
// Setter and Getter-methods
public static String getHost() {
return host;
}
public static void setHost(String host) {
FtpUpDown.host = host;
}
public static String getUsername() {
return username;
}
public static void setUsername(String username) {
FtpUpDown.username = username;
}
public static int getPort() {
return port;
}
public static void setPort(int port) {
FtpUpDown.port = port;
}
public static String getPassword() {
return password;
}
public static void setPassword(String password) {
FtpUpDown.password = password;
}
}
You create a new connection for every operation, and some code paths don't set the "binary file" flag (i.e. the upload and download methods).
setFileType(FTPClient.BINARY_FILE_TYPE) is the right thing to do, but in the code you provided it is only being done in connect(), which isn't called in download().
Even if it was called in download(), disconnect() is called which ends the session.
Bottom line: You need to call setFileType(FTPClient.BINARY_FILE_TYPE) in the download() method and the upload() method after ftpClient.connect(host, port)
For multiple image retrieval I am calling a PhotoHelperServlet with an anchor tag to get imageNames(multiple images) as follows
PhotoHelperServlet to get names of Images
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// Getting userid from session
Image image = new Image();
image.setUserid(userid);
ImageDAO imageDAO = new ImageDAO();
try {
List<Image> imageId = imageDAO.listNames(image);
if (imageId == null) {
// check if imageId is retreived
}
request.setAttribute("imageId", imageId);
//Redirect it to home page
RequestDispatcher rd = request.getRequestDispatcher("/webplugin/jsp/profile/photos.jsp");
rd.forward(request, response);
catch (Exception e) {
e.printStackTrace();
}
In ImageDAO listNames() method :
public List<Image> listNames(Image image) throws IllegalArgumentException, SQLException, ClassNotFoundException {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultset = null;
Database database = new Database();
List<Image> imageId = new ArrayList<Image>();
try {
connection = database.openConnection();
preparedStatement = connection.prepareStatement(SQL_GET_PHOTOID);
preparedStatement.setLong(1, image.getUserid());
resultset = preparedStatement.executeQuery();
while(resultset.next()) {
image.setPhotoid(resultset.getLong(1));
imageId.add(image);
}
} catch (SQLException e) {
throw new SQLException(e);
} finally {
close(connection, preparedStatement, resultset);
}
return imageId;
}
In JSP code:
<c:forEach items="${imageId}" var="imageid">
<img src="Photos/${imageid}">
</c:forEach>
In PhotoServlet doGet() method to get a photo:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String imageid = request.getPathInfo().substring(1);
if(imageid == null) {
// check for null and response.senderror
}
ImageDAO imageDAO = new ImageDAO();
try {
Image image = imageDAO.getPhotos(imageid);
if(image == null) {}
BufferedInputStream input = null;
BufferedOutputStream output = null;
try {
input = new BufferedInputStream(image.getPhoto(), DEFAULT_BUFFER_SIZE);
output = new BufferedOutputStream(response.getOutputStream(), DEFAULT_BUFFER_SIZE);
// Write file contents to response.
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
int length;
while ((length = input.read(buffer)) > 0) {
output.write(buffer, 0, length);
}
} finally {
if (output != null) try { output.close(); } catch (IOException logOrIgnore) {}
if (input != null) try { input.close(); } catch (IOException logOrIgnore) {}
}
} catch(Exception e) {
e.printStackTrace();
}
In ImageDAO getPhotos() method
public Image getPhotos(String imageid) throws IllegalArgumentException, SQLException, ClassNotFoundException {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultset = null;
Database database = new Database();
Image image = new Image();
try {
connection = database.openConnection();
preparedStatement = connection.prepareStatement(SQL_GET_PHOTO);
preparedStatement.setString(1, imageid);
resultset = preparedStatement.executeQuery();
while(resultset.next()) {
image.setPhoto(resultset.getBinaryStream(1));
}
} catch (SQLException e) {
throw new SQLException(e);
} finally {
close(connection, preparedStatement, resultset);
}
return image;
}
In web.xml
<!-- Getting each photo -->
<servlet>
<servlet-name>Photos Module</servlet-name>
<servlet-class>app.controllers.PhotoServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Photos Module</servlet-name>
<url-pattern>/Photos/*</url-pattern>
</servlet-mapping>
<!-- Getting photo names -->
<servlet>
<servlet-name>Photo Module</servlet-name>
<servlet-class>app.controllers.PhotoHelperServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Photo Module</servlet-name>
<url-pattern>/Photo</url-pattern>
</servlet-mapping>
Question:
I am getting following Exception:
java.io.IOException: Stream closed
on this Line:
at app.controllers.PhotoServlet.doGet(PhotoServlet.java:94)
while ((length = input.read(buffer)) > 0) {
The full Exception:
java.io.IOException: Stream closed
at java.io.BufferedInputStream.getInIfOpen(BufferedInputStream.java:134)
at java.io.BufferedInputStream.read1(BufferedInputStream.java:256)
at java.io.BufferedInputStream.read(BufferedInputStream.java:317)
at java.io.FilterInputStream.read(FilterInputStream.java:90)
at app.controllers.PhotoServlet.doGet(PhotoServlet.java:94)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:304)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:240)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:164)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:498)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:164)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:562)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:394)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:243)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:188)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:166)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:302)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:662)
I'd imagine that the basic code flow is laid out like follows:
try {
Get connection, statement, resultset
Use connection, statement, resultset
Get inputstream of resultset
} finally {
Close resultset, statement, connection
}
try {
Get outputstream
Use inputstream of resultset, outputstream
} finally {
Close outputstream, inputstream of resultset
}
And that the close of the ResultSet has implicitly closed the InputStream. It look like that your JDBC driver does not store the InputStream of the ResultSet fully in memory or on temp storage when the ResultSet is closed. Perhaps the JDBC driver is a bit simplistic, or not well thought designed, or the image is too large to be stored in memory. Who knows.
I'd first figure out what JDBC driver impl/version you're using and then consult its developer documentation to learn about settings which may be able to change/fix this behaviour. If you still can't figure it out, then you'd have to rearrange the basic code flow as follows:
try {
Get connection, statement, resultset
Use connection, statement, resultset
try {
Get inputstream of resultset, outputstream
Use inputstream of resultset, outputstream
} finally {
Close outputstream, inputstream of resultset
}
} finally {
Close resultset, statement, connection
}
Or
try {
Get connection, statement, resultset
Use connection, statement, resultset
Get inputstream of resultset
Copy inputstream of resultset
} finally {
Close resultset, statement, connection
}
try {
Get outputstream
Use copy of inputstream, outputstream
} finally {
Close outputstream, copy of inputstream
}
The first approach is the most efficient, only the code is clumsy. The second approach is memory inefficient when you're copying to ByteArrayOutputStream, or performance inefficient when you're copying to FileOutputStream. If the images are mostly small and do not exceed a megabyte or something, then I'd just copy it to ByteArrayOutputStream.
InputStream input = null;
OutputStream output = null;
try {
input = new BufferedInputStream(resultSet.getBinaryStream("columnName"), DEFAULT_BUFFER_SIZE);
output = new ByteArrayOutputStream();
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
for (int length; ((length = input.read(buffer)) > 0;) {
output.write(buffer, 0, length);
}
} finally {
if (output != null) try { output.close(); } catch (IOException ignore) {}
if (input != null) try { input.close(); } catch (IOException ignore) {}
}
Image image = new Image();
image.setPhoto(new ByteArrayInputStream(output.toByteArray()));
// ...
参考一下:
ImageLoad
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.net.Uri;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.ImageSwitcher;
import android.widget.ImageView;
/**
* 图片加载帮助类(自动异步加载、图片文件缓存、缓存文件管理)
*
* #author n.zhang
*
*/
public class ImageLoad {
private static final String TAG = "imageLoad";// 日志标签
private static final String TAG_REF = TAG + "Ref";
private Executor executor; // 线程池
private int defaultImageID;// 默认图片id
private Context context;// 你懂的
private HashMap<String, PathInfo> cache = new HashMap<String, PathInfo>();// URL
boolean sdCardExist = Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED); // 路径信息对应表
private LinkedList<PathInfo> use = new LinkedList<PathInfo>();// 已在使用的路径信息队列
private LinkedList<PathInfo> lost = new LinkedList<PathInfo>();// 还未使用的路径信息队列
private LinkedList<PathInfo> original = new LinkedList<PathInfo>();// 初始图片路径信息队列
private int index = 0;// id下标
/**
* 图片加载工具,默认10线程下载,缓存80张图片
*
* #param context
*/
public ImageLoad(Context context) {
this(context, 10, 80, 0);
}
/**
* 图片加载工具
*
* #param context
* 你懂的
* #param threadSize
* 最大线程数
* #param maxCacheSize
* 最大缓存图片数量
* #param defaultImageID
* 默认图片id
*/
public ImageLoad(Context context, int threadSize, int maxCacheSize, int defaultImageID) {
this.context = context;
this.defaultImageID = defaultImageID;
executor = Executors.newFixedThreadPool(threadSize);
loadImagePathInfo();
// 图片信息数量不足不满最大值,以空白图片信息补足。
newImagePathInfo(maxCacheSize);
for (PathInfo pi : original) {
if (null == pi.url) {
lost.offer(pi);
} else {
use.offer(pi);
cache.put(pi.url, pi);
}
}
File dir = null;
if (sdCardExist) {
dir = new File(Environment.getExternalStorageDirectory() + "/t_image/");
} else {
dir = new File(context.getCacheDir() + "/t_image/");
}
// 如果文件存在并且不是目录,则删除
if (dir.exists() && !dir.isDirectory()) {
dir.delete();
}
// 如果目录不存在,则创建
if (!dir.exists()) {
dir.mkdir();
}
}
/**
* 路径信息
*
* #author n.zhang
*
*/
public static class PathInfo {
private int id;// 图片id 此id用于生成存储图片的文件名。
private String url;// 图片url
}
/**
* 获得图片存储路径
*
* #param url
* #return
*/
public PathInfo getPath(String url) {
PathInfo pc = cache.get(url);
if (null == pc) {
pc = lost.poll();
}
if (null == pc) {
pc = use.poll();
refresh(pc);
}
return pc;
}
/**
* #info 微博使用加载数据路径
* #author FFMobile-cuihe
* #date 2012-3-1 下午2:13:10
* #Title: getsPath
* #Description: TODO
* #param#param url
* #param#return 设定文件
* #return PathInfo 返回类型
* #throws
*/
public PathInfo getsPath(String url) {
PathInfo pc = cache.get(url);
if (null == pc) {
pc = lost.peek();
}
// if (null == pc) {
// pc = use.peek();
// refresh(pc);
// }
return pc;
}
public PathInfo getLocalPath(String url) {
PathInfo pc = cache.get(url);
if (null == pc) {
pc = lost.peek();
}
return pc;
}
/**
* 刷新路径信息(从索引中删除对应关系、删除对应的图片文件、获取一个新id)
*
* #param pc
*/
private void refresh(PathInfo pc) {
long start = System.currentTimeMillis();
File logFile = null;
try {
cache.remove(pc.url);
File file = toFile(pc);
file.delete();
logFile = file;
pc.id = index++;
pc.url = null;
} finally {
Log.d(TAG_REF, "ref time {" + (System.currentTimeMillis() - start) + "}; ref {" + logFile + "}");
}
}
/**
* 获得file对象
*
* #param pi
* 路径缓存
* #return
*/
public File toFile(PathInfo pi) {
if (sdCardExist) {
return new File(Environment.getExternalStorageDirectory() + "/t_image/" + pi.id + ".jpg");
} else {
return new File(context.getCacheDir() + "/t_image/" + pi.id + ".jpg");
}
}
/**
* 请求加载图片
*
* #param url
* #param ilCallback
*/
public void request(String url, final ILCallback ilCallback) {
final long start = System.currentTimeMillis();
final PathInfo pc = getPath(url);
File file = toFile(pc);
if (null != pc.url) {
ilCallback.seed(Uri.fromFile(file));
Log.d(TAG, "load time {" + (System.currentTimeMillis() - start) + "}; cache {" + pc.url + "} ");
} else {
pc.url = url;
Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
if (null == msg.obj) {
ilCallback.seed(Uri.EMPTY);
Log.d(TAG, "load lost time {" + (System.currentTimeMillis() - start) + "}; network lost {"
+ pc.url + "}");
} else {
ilCallback.seed((Uri) msg.obj);
Log.d(TAG, "load time {" + (System.currentTimeMillis() - start) + "}; network {" + pc.url + "}");
}
};
};
executor.execute(new DownloadImageTask(pc, file, mHandler));
}
}
private void localRequest(String url, final ILCallback ilCallback) {
final long start = System.currentTimeMillis();
final PathInfo pc = getLocalPath(url);
File file = toFile(pc);
if (null != pc.url) {
ilCallback.seed(Uri.fromFile(file));
Log.d(TAG, "load time {" + (System.currentTimeMillis() - start) + "}; cache {" + pc.url + "} ");
}
}
public void localRequest(String url, ImageView iv) {
localRequest(url, new ImageViewCallback(iv));
}
/**
* 请求加载图片
*
* #param url
* #param iv
*/
public void request(String url, ImageView iv) {
request(url, new ImageViewCallback(iv));
}
/**
* 请求加载图片
*
* #param url
* #param iv
*/
// public void request(String url, ImageButton iv) {
// request(url, new ImageButtonCallbacks(iv));
// }
/**
* 请求加载图片
*
* #param url
* #param iv
*/
// public void request(String url, Button iv) {
// request(url, new ButtonCallbacks(iv));
// }
/**
* 请求加载图片
*
* #param url
* #param iv
*/
public void request(String url, ImageSwitcher iv) {
request(url, new ImageSwitcherCallbacks(iv));
}
/**
* 下载图片任务
*
* #author Administrator
*
*/
private class DownloadImageTask implements Runnable {
private Handler hc;
private PathInfo pi;
private File file;
public DownloadImageTask(PathInfo pi, File file, Handler hc) {
this.pi = pi;
this.file = file;
this.hc = hc;
}
public void run() {
try {
byte[] b = requestHttp(pi.url);
if (null == b) {
throw new IOException("数据为空");
}
writeFile(file, b);
use.offer(pi);
cache.put(pi.url, pi);
Message message = new Message();
message.obj = Uri.fromFile(file);
hc.sendMessage(message);
} catch (IOException e) {
Message message = hc.obtainMessage(0, Uri.EMPTY);
hc.sendMessage(message);
Log.i(TAG, "image download lost.", e);
} catch (RuntimeException e) {
Message message = hc.obtainMessage(0, Uri.EMPTY);
hc.sendMessage(message);
Log.i(TAG, "image download lost.", e);
}
}
}
private void writeFile(File file, byte[] data) throws IOException {
FileOutputStream out = new FileOutputStream(file);
try {
out.write(data);
} finally {
out.close();
}
}
private static byte[] requestHttp(String url) throws IOException {
DefaultHttpClient client = new DefaultHttpClient();
System.gc();
try {
HttpGet get = new HttpGet(url);
HttpResponse res = client.execute(get);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
if (200 == res.getStatusLine().getStatusCode()) {
res.getEntity().writeTo(baos);
return baos.toByteArray();
} else {
throw new IOException("httpStatusCode:" + res.getStatusLine().getStatusCode());
}
} finally {
client.getConnectionManager().shutdown();
}
}
/**
* 读取图片路径信息
*
* #return
*/
#SuppressWarnings("unchecked")
private void loadImagePathInfo() {
long start = System.currentTimeMillis();
File file = new File(context.getCacheDir() + "/imagePathCache.json");
try {
if (!file.isFile()) {
// 文件不存在。
Log.d(TAG, "path info file does not exist");
imageGc();
return;
}
StringWriter sw = new StringWriter();
char[] buf = new char[1024];
int len;
FileReader fr = new FileReader(file);
while (-1 != (len = fr.read(buf))) {
sw.write(buf, 0, len);
}
fr.close();
JSONObject json = new JSONObject(sw.toString());
Iterator<String> it = json.keys();
while (it.hasNext()) {
String key = it.next();
int id = json.getInt(key);
PathInfo pi = new PathInfo();
pi.url = key;
pi.id = id;
if (index < id) {
index = id;
}
original.add(pi);
}
// 打开文件文件缓存成功
Log.i(TAG, "load path info ok.");
} catch (IOException e) {
Log.i(TAG, "load path info lost - IOException.", e);
imageGc();
} catch (JSONException e) {
Log.i(TAG, "load path info lost - JSONException.", e);
imageGc();
} finally {
if (file.exists()) {
file.delete();
Log.d(TAG, "delete path info file");
}
Log.d(TAG, "load path info time {" + (System.currentTimeMillis() - start) + "}");
}
}
/**
* 如果路径信息加载失败,清理图片目录。
*/
private void imageGc() {
long start = System.currentTimeMillis();
try {
File dir;
if (sdCardExist) {
dir = new File(Environment.getExternalStorageDirectory() + "/t_image/");
} else {
dir = new File(context.getCacheDir() + "/t_image/");
}
if (dir.isDirectory()) {
for (File file : dir.listFiles()) {
file.delete();
// gc
Log.d(TAG_REF, "gc {" + file + "}");
}
}
} finally {
// gc 计时
Log.d(TAG_REF, "gc time {" + (System.currentTimeMillis() - start) + "}");
}
}
private void newImagePathInfo(int max_size) {
for (int i = original.size(); i < max_size; i++) {
PathInfo pc = new PathInfo();
pc.id = index++;
original.add(pc);
}
}
/**
* 保存图片路径信息(如记录,下次程序打开,可读取该记录已存图片继续可用)
*/
public void saveImagePathInfo() {
long start = System.currentTimeMillis();
try {
JSONObject json = new JSONObject();
for (PathInfo pi : use) {
try {
json.put(pi.url, pi.id);
} catch (JSONException e) {
e.printStackTrace();
}
}
File file = new File(context.getCacheDir() + "/imagePathCache.json");
try {
FileWriter fw = new FileWriter(file);
fw.write(json.toString());
fw.close();
Log.i(TAG, "image file info save ok.");
} catch (IOException e) {
e.printStackTrace();
Log.i(TAG, "image file info save lost.");
file.delete();
}
} finally {
Log.d(TAG, "save time {" + (System.currentTimeMillis() - start) + "}");
}
}
/**
* 图片加载回调
*
* #author n.zhang
*
*/
public static interface ILCallback {
public void seed(Uri uri);
}
private class ImageViewCallback implements ILCallback {
public ImageViewCallback(ImageView iv) {
if (defaultImageID > 0) {
iv.setImageResource(defaultImageID);
}
this.iv = iv;
}
private ImageView iv;
public void seed(Uri uri) {
File f = new File(uri.getPath());
iv.setImageURI(Uri.parse(f.toString()));
f = null;
}
}
// private class ImageButtonCallbacks implements ILCallback {
// public ImageButtonCallbacks(ImageButton iv) {
// if (defaultImageID > 0) {
// iv.setBackgroundResource(defaultImageID);
////iv.setImageResource(defaultImageID);
// }
// this.iv = iv;
// }
//
// private ImageButton iv;
//
// public void seed(Uri uri) {
// iv.setImageURI(uri);
// }
// }
// private class ButtonCallbacks implements ILCallback {
// public ButtonCallbacks(Button iv) {
// if (defaultImageID > 0) {
// iv.setBackgroundResource(defaultImageID);
////iv.setImageResource(defaultImageID);
// }
// this.iv = iv;
// }
//
// private Button iv;
//
// public void seed(Uri uri) {
// iv.setImageURI(uri);
// }
// }
private class ImageSwitcherCallbacks implements ILCallback {
public ImageSwitcherCallbacks(ImageSwitcher iv) {
if (defaultImageID > 0) {
iv.setImageResource(defaultImageID);
}
this.iv = iv;
}
private ImageSwitcher iv;
public void seed(Uri uri) {
iv.setImageURI(uri);
}
}
}
It looks as if the problem is actually not in the code you posted. For some reason the stream input is closed. So you are probably closing the stream in image.getPhoto()