文件上的加密/解密操作在Android平台上产生奇怪的效果

问题描述 投票:0回答:1

我正在使用一个需要对文件系统上的文件进行加密(然后解密)的Android应用程序。我编写了一个android测试来测试在网上找到的代码,并根据需要进行调整。我尝试加密一个简单的文本,然后尝试对其解密。问题是,当我尝试对其进行解密时,要加密/解密的内容的开头出现了一些奇怪的字符。例如,我尝试对这样的字符串进行加密/解密:

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)

我收到了

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)

测试代码为

@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());
}

课程代码是:

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

关于我错了的任何主意吗?预先谢谢你

java android encryption cryptography
1个回答
0
投票

最后,我自己解决。我发布解决方案只是为了帮助将来会寻找类似情况的任何人。我错误地用encrypt方法检索了iv数组,我在生成另一个iv向量,而不是使用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();
}
© www.soinside.com 2019 - 2024. All rights reserved.