CipherInputStream 中的 skip 方法
skip method in CipherInputStream
我 运行 遇到一个问题,如果使用 CipherInputStream,则针对由 FileInputStream 支持的 InputStream 工作的代码将不起作用。
示例如下:
// skipCount is same as n in a FileInputStream
FileInputStream fis;
...
skipCount = fis.skip(n)
如果使用 CipherInputStream,行为会有所不同
// skipCount is always 0
CipherInputStream cis;
...
skipCount = cis.skip(n)
经过进一步调试,skip 似乎只有在与 read() 调用结合使用时才有效(即,return 值 > 0)。
有没有更好的方法让 skip 与 CipherInputStream 一起工作,而不是滚动我自己的 "skip" 依赖于调用 read 的方法?
此外,有没有办法告诉 CipherInputStream 自动执行 "read" 作为调用跳过调用的一部分?否则看起来跳过 API 在 CipherInputStream 中是不稳定的。
MCVE
public class TestSkip {
public static final String ALGO = "AES/CBC/PKCS5Padding";
public static final String CONTENT = "Strive not to be a success, but rather to be of value";
private static int BlockSizeBytes = 16;
private static SecureRandom random = null;
static {
try {
random = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Could not initialize AES encryption", e);
}
}
static byte[] getKeyBytes() throws NoSuchAlgorithmException, UnsupportedEncodingException
{
byte[] key = "Not a secure string!".getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key, 16); // use only first 128 bit
return key;
}
static KeySpec getKeySpec() throws GeneralSecurityException, UnsupportedEncodingException
{
return new SecretKeySpec(getKeyBytes(), "AES");
}
static byte[] getIv ()
{
byte[] iv = new byte[BlockSizeBytes];
random.nextBytes(iv);
return iv;
}
static Cipher initCipher (int mode, byte[] iv) throws GeneralSecurityException, UnsupportedEncodingException
{
KeySpec spec = getKeySpec();
Cipher cipher = Cipher.getInstance(ALGO);
cipher.init(mode, (SecretKey) spec, new IvParameterSpec(iv));
return cipher;
}
static void encrypt(String fileName) throws
GeneralSecurityException,
IOException
{
FileOutputStream fos = new FileOutputStream(fileName);
byte[] iv = getIv();
fos.write(iv);
Cipher cipher = initCipher(Cipher.ENCRYPT_MODE, iv);
CipherOutputStream cos = new CipherOutputStream(fos, cipher);
PrintWriter pw = new PrintWriter(cos);
pw.println(CONTENT);
pw.close();
}
static void skipAndCheck(String fileName) throws
GeneralSecurityException,
IOException
{
FileInputStream fis = new FileInputStream(fileName);
byte[] iv = new byte[BlockSizeBytes];
if (fis.read(iv) != BlockSizeBytes) {
throw new GeneralSecurityException("Could not retrieve IV from AES encrypted stream");
}
Cipher cipher = initCipher(Cipher.DECRYPT_MODE, iv);
CipherInputStream cis = new CipherInputStream(fis, cipher);
// This does not skip
long count = cis.skip(32);
System.out.println("Bytes skipped: " + count);
// Read a line
InputStreamReader is = new InputStreamReader(cis);
BufferedReader br = new BufferedReader(is);
String read = br.readLine();
System.out.println("Content after skipping 32 bytes is: " + read);
br.close();
}
static InputStream getWrapper(CipherInputStream cis) {
return new SkipInputStream(cis);
}
public static void main(String[] args) throws
IOException,
GeneralSecurityException
{
String fileName = "EncryptedSample.txt";
encrypt(fileName);
skipAndCheck(fileName);
}
}
找到适合我的解决方案。
创建了一个包装器 class,它扩展了 FilterInputStream 并使用在 InputStream.java
中找到的相同代码实现了 skip 方法
包装纸class
public class SkipInputStream extends FilterInputStream
{
private static final int MAX_SKIP_BUFFER_SIZE = 2048;
/**
* Creates a <code>FilterInputStream</code>
* by assigning the argument <code>in</code>
* to the field <code>this.in</code> so as
* to remember it for later use.
*
* @param in the underlying input stream, or <code>null</code> if
* this instance is to be created without an underlying stream.
*/
protected SkipInputStream (InputStream in)
{
super(in);
}
/**
* Same implementation as InputStream#skip
*
* @param n
* @return
* @throws IOException
*/
public long skip(long n)
throws IOException
{
long remaining = n;
int nr;
if (n <= 0) {
return 0;
}
int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
byte[] skipBuffer = new byte[size];
while (remaining > 0) {
nr = in.read(skipBuffer, 0, (int)Math.min(size, remaining));
if (nr < 0) {
break;
}
remaining -= nr;
}
return n - remaining;
}
}
有了这个 class 只需更改上面 MCVE 中的以下行
long count = cis.skip(32);
到
long count = getWrapper(cis).skip(32);
输出
旧
Bytes skipped: 0
Content after skipping 32 bytes is: Strive not to be a success, but rather to be of value
新
Bytes skipped: 32
Content after skipping 32 bytes is: rather to be of value
我 运行 遇到一个问题,如果使用 CipherInputStream,则针对由 FileInputStream 支持的 InputStream 工作的代码将不起作用。
示例如下:
// skipCount is same as n in a FileInputStream
FileInputStream fis;
...
skipCount = fis.skip(n)
如果使用 CipherInputStream,行为会有所不同
// skipCount is always 0
CipherInputStream cis;
...
skipCount = cis.skip(n)
经过进一步调试,skip 似乎只有在与 read() 调用结合使用时才有效(即,return 值 > 0)。
有没有更好的方法让 skip 与 CipherInputStream 一起工作,而不是滚动我自己的 "skip" 依赖于调用 read 的方法?
此外,有没有办法告诉 CipherInputStream 自动执行 "read" 作为调用跳过调用的一部分?否则看起来跳过 API 在 CipherInputStream 中是不稳定的。
MCVE
public class TestSkip {
public static final String ALGO = "AES/CBC/PKCS5Padding";
public static final String CONTENT = "Strive not to be a success, but rather to be of value";
private static int BlockSizeBytes = 16;
private static SecureRandom random = null;
static {
try {
random = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Could not initialize AES encryption", e);
}
}
static byte[] getKeyBytes() throws NoSuchAlgorithmException, UnsupportedEncodingException
{
byte[] key = "Not a secure string!".getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key, 16); // use only first 128 bit
return key;
}
static KeySpec getKeySpec() throws GeneralSecurityException, UnsupportedEncodingException
{
return new SecretKeySpec(getKeyBytes(), "AES");
}
static byte[] getIv ()
{
byte[] iv = new byte[BlockSizeBytes];
random.nextBytes(iv);
return iv;
}
static Cipher initCipher (int mode, byte[] iv) throws GeneralSecurityException, UnsupportedEncodingException
{
KeySpec spec = getKeySpec();
Cipher cipher = Cipher.getInstance(ALGO);
cipher.init(mode, (SecretKey) spec, new IvParameterSpec(iv));
return cipher;
}
static void encrypt(String fileName) throws
GeneralSecurityException,
IOException
{
FileOutputStream fos = new FileOutputStream(fileName);
byte[] iv = getIv();
fos.write(iv);
Cipher cipher = initCipher(Cipher.ENCRYPT_MODE, iv);
CipherOutputStream cos = new CipherOutputStream(fos, cipher);
PrintWriter pw = new PrintWriter(cos);
pw.println(CONTENT);
pw.close();
}
static void skipAndCheck(String fileName) throws
GeneralSecurityException,
IOException
{
FileInputStream fis = new FileInputStream(fileName);
byte[] iv = new byte[BlockSizeBytes];
if (fis.read(iv) != BlockSizeBytes) {
throw new GeneralSecurityException("Could not retrieve IV from AES encrypted stream");
}
Cipher cipher = initCipher(Cipher.DECRYPT_MODE, iv);
CipherInputStream cis = new CipherInputStream(fis, cipher);
// This does not skip
long count = cis.skip(32);
System.out.println("Bytes skipped: " + count);
// Read a line
InputStreamReader is = new InputStreamReader(cis);
BufferedReader br = new BufferedReader(is);
String read = br.readLine();
System.out.println("Content after skipping 32 bytes is: " + read);
br.close();
}
static InputStream getWrapper(CipherInputStream cis) {
return new SkipInputStream(cis);
}
public static void main(String[] args) throws
IOException,
GeneralSecurityException
{
String fileName = "EncryptedSample.txt";
encrypt(fileName);
skipAndCheck(fileName);
}
}
找到适合我的解决方案。
创建了一个包装器 class,它扩展了 FilterInputStream 并使用在 InputStream.java
中找到的相同代码实现了 skip 方法包装纸class
public class SkipInputStream extends FilterInputStream
{
private static final int MAX_SKIP_BUFFER_SIZE = 2048;
/**
* Creates a <code>FilterInputStream</code>
* by assigning the argument <code>in</code>
* to the field <code>this.in</code> so as
* to remember it for later use.
*
* @param in the underlying input stream, or <code>null</code> if
* this instance is to be created without an underlying stream.
*/
protected SkipInputStream (InputStream in)
{
super(in);
}
/**
* Same implementation as InputStream#skip
*
* @param n
* @return
* @throws IOException
*/
public long skip(long n)
throws IOException
{
long remaining = n;
int nr;
if (n <= 0) {
return 0;
}
int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
byte[] skipBuffer = new byte[size];
while (remaining > 0) {
nr = in.read(skipBuffer, 0, (int)Math.min(size, remaining));
if (nr < 0) {
break;
}
remaining -= nr;
}
return n - remaining;
}
}
有了这个 class 只需更改上面 MCVE 中的以下行
long count = cis.skip(32);
到
long count = getWrapper(cis).skip(32);
输出
旧
Bytes skipped: 0
Content after skipping 32 bytes is: Strive not to be a success, but rather to be of value
新
Bytes skipped: 32
Content after skipping 32 bytes is: rather to be of value