Расшифровка возвращает пустую строку при шифровании текста в Android

Я пытаюсь безопасно сохранить несколько текстовых полей. Для этого я пытаюсь зашифровать и расшифровать содержимое. Это код:

public class SecureStorage {

    public String getPassword() {
        if(!isRooted()) {
            String password = pref.getPassword("");
            System.out.println("pass getPass: " + password);
            return password.isEmpty() ? password : new String(decrypt(Base64.decode(password, Base64.DEFAULT)));

        } else
            return "";
    }

    public void setPassword(String passwordStr) {
        if(!isRooted()) {
            byte[] password = encrypt(passwordStr.getBytes());
            pref.setPassword(password == null ? "" : Base64.encodeToString(password, Base64.DEFAULT));
        }
    }

    private SecretKey generateKey() {
        // Generate a 256-bit key
        final int outputKeyLength = 256;
        try {
            SecureRandom secureRandom = new SecureRandom();
            // Do *not* seed secureRandom! Automatically seeded from system entropy.
            KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
            keyGenerator.init(outputKeyLength, secureRandom);
            return keyGenerator.generateKey();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }

    private byte[] getRawKey(byte[] key) throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG", "Crypto");
        secureRandom.setSeed(key);
        keyGenerator.init(128, secureRandom); // 192 and 256 bits may not be available
        SecretKey secretKey = keyGenerator.generateKey();
        byte[] rawKey = secretKey.getEncoded();
        return rawKey;
    }

    /** The method that encrypts the string.
     @param toEncrypt The string to be encrypted.
     @return The encrypted string in bytes. */
    //****************************************
    private byte[] encrypt(byte[] toEncrypt) {
        byte[] encryptedByte = new String().getBytes();
        try {
            SecretKeySpec secretKeySpec = new SecretKeySpec(getRawKey(Utils.generateUID().getBytes()), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
            encryptedByte = cipher.doFinal(toEncrypt);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return encryptedByte;
    }

    //**************************************
    /** The method that decrypts the string.
     @param encryptedByte The string to be encrypted.
     @return The decrypted string in bytes. */
    //****************************************
    private byte[] decrypt(byte[] encryptedByte) {
        byte[] decryptedByte = new String().getBytes();
        try {
            SecretKeySpec secretKeySpec = new SecretKeySpec(getRawKey(Utils.generateUID().getBytes()), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
            decryptedByte = cipher.doFinal(encryptedByte);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return decryptedByte;
    }
}

Я могу зашифровать текст. Я использую SharedPreferences для хранения зашифрованного текста и получаю общие настройки для расшифровки текста и передачи его в TextView. Но в getPassword() я получаю значение SharedPreference и пытаюсь расшифровать, есть ли какое-либо значение в SharedPrefs. Я получаю SharedPrefs в строку (password) и пытаюсь ее расшифровать, но не могу! Я получаю пустую строку!


person Adithya.K    schedule 20.06.2016    source источник
comment
что делает pref.getPassword("");?   -  person Jimmy    schedule 21.06.2016
comment
Вы получаете пустую строку, потому что перехватываете все возможные исключения. Вы смотрели в свои журналы, чтобы увидеть, какое исключение было выбрано? Я подозреваю, что это BadPaddingException   -  person Artjom B.    schedule 21.06.2016
comment
понял! Я генерирую случайный ключ и не сохраняю его! Итак, теперь, когда я перезваниваю, новый ключ. Теперь я сохраняю ключ вместе с текстом!   -  person Adithya.K    schedule 22.06.2016


Ответы (1)


Для работы режима CBC требуется вектор инициализации (IV). IV предназначен для рандомизации зашифрованного текста и предотвращения определения злоумышленником того, имели ли предыдущие открытые тексты тот же префикс, что и текущий.

Поскольку вы не генерируете IV, он будет создан для вас. Неправильный IV влияет только на первый блок (первые 16 байтов для AES). Если ваш открытый текст короче блока, то это приведет к совершенно другому расшифровыванию и тогда отступы не убрать с вероятностью примерно 255/256.

IV не должен быть секретным. Обычно его добавляют перед зашифрованным текстом и вырезают перед расшифровкой.

public byte[] encrypt(byte[] toEncrypt) throws Exception {
    try {
        SecretKeySpec secretKeySpec = new SecretKeySpec(getRawKey(Utils.generateUID().getBytes()), "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
        byte[] iv = cipher.getIV();
        byte[] ct = cipher.doFinal(toEncrypt);

        byte[] result = new byte[ct.length + iv.length];
        System.arraycopy(iv, 0, result, 0, iv.length);
        System.arraycopy(ct, 0, result, iv.length, ct.length);
        return result;
    } catch(...) {...}
    return new byte[0];
}

public byte[] decrypt(byte[] encryptedByte) throws Exception {
    try {
        SecretKeySpec secretKeySpec = new SecretKeySpec(getRawKey(Utils.generateUID().getBytes()), "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

        byte[] iv = new byte[cipher.getBlockSize()];
        byte[] ct = new byte[encryptedByte.length - cipher.getBlockSize()];
        System.arraycopy(encryptedByte, 0, iv, 0, cipher.getBlockSize());
        System.arraycopy(encryptedByte, cipher.getBlockSize(), ct, 0, ct.length);

        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(iv));
        return cipher.doFinal(ct);
    } catch (...) {...}
    return new byte[0];
}

Проблема может заключаться в том, что зашифрованный текст больше ожидаемого (дополнительно 16 байт для IV). Если вы можете убедиться, что злоумышленник не получит никакой полезной информации, определив, что предыдущие открытые тексты имели тот же префикс, вы можете использовать статический IV. Но имейте в виду, что обычно это не очень хорошая идея, и ее следует делать только в том случае, если вам действительно нужно это пространство.

private static final byte[] IV = new byte[16];
...
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, new IvParameterSpec(IV));
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(IV));
person Artjom B.    schedule 21.06.2016