我遇到一个问题,如果使用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)

经过进一步调试后,如果与read()调用结合使用,则skip只会起作用(即,返回值> 0)。

有没有比滚动依靠调用read的我自己的“skip”方法更好的让CipherInputStream跳过的方法呢?

另外,有没有一种方法可以告诉CipherInputStream在调用跳过调用的过程中自动执行“读取”操作?否则,看起来好像跳过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);
  }

}

最佳答案

找到了对我有用的解决方案。

创建了一个包装类,该类扩展了FilterInputStream并使用与InputStream.java中相同的代码实现了skip方法

包装类

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;
    }

}

对于此类,只需要在上面的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

关于java - CipherInputStream中的skip方法,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/30856112/

10-08 22:40