Почему шифрование Java Cipher с одинаковой строкой, ключом и SecureRandom всегда отличается?

Спасибо за комментарии от всех.

Однако я должен объяснить этот вопрос.

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

Дело в том, что мне нужно войти на сервер с учетной записью / паролем, выполнив следующие действия:

  1. Получить информацию с сервера: abcom / get_cipher.cgi.
  2. получить ответ, разобрать его и получить некоторую информацию для создания шифра.
  3. используйте шифр для шифрования учетной записи / пароля, а затем составьте следующий URL abcom/login.cgi?encrypted={encrypted_account_password}

Это сложный процесс, и я не могу просить сервер изменить протокол. Я хочу протестировать весь процесс входа в систему. Таким образом, я попытался предоставить поддельную учетную запись / пароль и проверить правильность созданного URL-адреса без расшифровки результата (если расшифровывает результат, это означает, что в этом тестовом примере мне нужно расшифровать зашифрованный результат, проанализировать URL-адрес, и извлеките связанную информацию, слишком много не связанной с тестированием. Более того, если я внесу некоторые изменения в процесс входа в систему, мне может понадобиться изменить процесс расшифровки и анализа в тестовых примерах.)

Это означает, что хеш-функция не подходит для меня. (Исходный процесс входа в систему не использует хеш, поэтому я не хочу проверять его в тестовых случаях. Более того, даже если я проверю, что результат хеширования правильный, это не доказывает, что процесс входа в систему правильный).

=== Оригинальный вопрос выглядит следующим образом ===

У меня есть программа, которая требует входа в систему. Чтобы аннулировать передачу пароля в сети обычным текстом, мне нужно его зашифровать. Другими словами, процесс входа в систему содержит этап шифрования.

Затем я хочу написать контрольный пример для всего процесса входа в систему. Я думаю, что результат шифрования будет таким же, если он использует ту же учетную запись и пароль.

Так как он может использовать SecureRandom в процессе шифрования, я пишу поддельный SecureRandom от Mockito в виде следующего кода:

private static final long RANDOM_SEED = 3670875202692512518L;
private Random generateRandomWithFixSeed() {
    Random random = new Random(RANDOM_SEED);
    return random;
}

private SecureRandom generateSecureRandomWithFixSeed() {
    final Random random = generateRandomWithFixSeed();
    final SecureRandom secureRandom = new SecureRandom();
    final SecureRandom spySecureRandom = Mockito.spy(secureRandom);

    Mockito.doAnswer(new Answer<Object>() {
        @Override
        public Object answer(InvocationOnMock invocation) throws Throwable {
            Object[] args = invocation.getArguments();
            byte[] bytes = (byte[]) args[0];
            random.nextBytes(bytes);
            return bytes;
        }
    })
            .when(spySecureRandom)
            .nextBytes(Matchers.<byte[]>anyObject());

    return spySecureRandom;
}

@Test
public void test_SecureRandom_WithFixSeed() {
    final SecureRandom secureRandom1 = generateSecureRandomWithFixSeed();
    final SecureRandom secureRandom2 = generateSecureRandomWithFixSeed();

    final byte[] bytes1 = new byte[20];
    final byte[] bytes2 = new byte[20];

    secureRandom1.nextBytes(bytes1);
    secureRandom2.nextBytes(bytes2);

    boolean isTheSameSeries = true;
    for(int i = 0; i < 20; i++) {
        isTheSameSeries &= (bytes1[i]==bytes2[i]);
    }

    assertThat(isTheSameSeries, is(true));
}

GenerateRandomWithFixSeed() создаст новый объект Random с тем же ключом, чтобы сгенерировать тот же результат. GenerateSecureRandomWithFixSeed () использует Makito для обнаружения вызова функции nextBytes() и всегда отвечает на случайный результат. test test_SecureRandom_WithFixSeed () также показывает, что два разных экземпляра SecureRandom генерируют одинаковые результаты.

Однако, если я использую generateSecureRandomWithFixSeed() в шифре, как показано ниже, он не всегда может возвращать один и тот же результат.

    @Test
public void test_cipher() {
    final SecureRandom secureRandomWithFixSeed = generateSecureRandomWithFixSeed();

    final String pkcs = "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAv7n+/uWHHVC7229QLEObeH0vUcOagavDukf/gkveqgZsszzGkZQaXfsrjdPiCnvjozCy1tbnLu5EInDy4w8B+a9gtK8KqsvlsfuaT9kRSMUS8CfgpWj8JcJwijmeZhjR52k0UBpWLfn3JmRYW8xjZW6dlOSnS0yqwREPU7myyqUzhk1vyyUG7wLpk7uK9Bxmup0tnbnD4MeqDboAXlQYnIFVV+CXywlAQfHHCfQRsGhsEtu4excZVw7FD1rjnro9bcWFY9cm/KdDBxZCYQoT/UW0OBbipoINycrmfMKt1r4mGE9/MdVoIEMBc54aI6sb2g5J2GtNCYfEu+1/gA99xY0+5B3ydH74cbqfHYOZIvu11Q7GnpZ6l8zTLlMuF/pvlSily76I45H0YZ3HcdQnf/GoKC942P6fNsynHEX01zASYM8dzyMxHQpNEx7fcXGi+uiBUD/Xdm2jwzr9ZEP5eEVlrpcAvr8c9S5ylE50lwR+Mp3vaZxPoLdSGZrfyXy4v97UZSnYARQBacmn6KgsIHIOKhYOxNgUG0jwCO/zrPvlbjiYTHQYLOCcldTULvXOdn51enJFGVjscGoZfRj6vZgyHVCUW4iip4iSbSKPcPbf0GMZuniS9qJ3Wybve0/xpppdOv1c40ez0NKQyQkEZRb+V0qfesatJKZd/hUGr+MCAwEAAQ==";
    final byte bytePKCS[] = Base64.base64ToByteArray(pkcs);
    final X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(bytePKCS);

    PublicKey pubKey = null;
    try {
        pubKey = KeyFactory.getInstance("RSA").generatePublic(pubKeySpec);
    } catch (InvalidKeySpecException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }

    final String targetResultText = "NZqTzuNli92vuXEQNchGeF6faN/NBHykhfqBFcWzBHZhbgljZaWAcAzasFSm/odZZ6vBD2jK7J4oi5BmDjxNdEjkXyv3OZ2sOTLCfudgPwXcXmmhOwWHDLY02OX0X3RwBHzqWczqAd4dwslo59Gp5CT59GWXenJPL8wvG90WH2XAKOmHg5uEZj55ZvErRQ6StPVzLkiNCMPOhga7FZWK/rSEpT6BHDy3CibDZ0PNRtAW4wiYAr0Cw6brqiqkN301Bz6DzrV5380KDHkw26GjM8URSTFekwvZ7FISQ72UaNHhjnh1WgMIPf/QDbrEh5b+rmdZjzc5bdjyONrQuoj0rzrWLN4z8lsrBnKFVo+zVyUeqr0IxqD2aHDLyz5OE4fb5IZJHEMfYr/R79Zfe8IuQ2tusA02ZlFzGRGBhAkb0VygXxJxPXkjbkPaLbZQZOsKLUoIDkcbNoUTxeS9+4LWVg1j5q3HR9OSvmsF5I/SszvVrnXdNaz1IKCfVYkwpIBQ+Y+xI/K360dWIHR/vn7TU4UsGmWtwVciq0jWLuBN/qRE6MV47TDRQu63GzeV00yAM/xUM33qWNXCV1tbGXNZw8jHpakgflTY0hcjOFHPpq2UfJCyxiSBtZ0b7hw9Rvhi8VwYc243jXO9CvGq+J6JYvchvKHjq2+YKn1UB2+gs20=";
    final String plainText = "a";
    String resultText = "";
    try {
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.ENCRYPT_MODE, pubKey, secureRandomWithFixSeed);
        final byte[] result = cipher.doFinal(plainText.getBytes());
        resultText = Base64.byteArrayToBase64(result);
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        e.printStackTrace();
    } catch (IllegalBlockSizeException e) {
        e.printStackTrace();
    } catch (BadPaddingException e) {
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        e.printStackTrace();
    }
    assertThat(resultText, is(targetResultText));
}

аа

3 ответа

Я нашел причину.

Я обнаружил, что если я запускаю тестовый пример на Android 4.4, он не может работать, но работает на 4.1.2.

Более подробное исследование показывает, что Android использует OpenSSL для шифрования / описания в более новой версии (AndroidOpenSSL).

В этом файле внешние / concerypt/src/main/java/org/concerypt/OpenSSLCipherRSA.java

@Override
protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
    engineInitInternal(opmode, key);
}

Это показывает, что он использует собственную библиотеку OpenSSL для шифрования и не использует данный SecureRandom.

Решение состоит в том, чтобы предоставить провайдеру Cipher.getInstance().

@Test
public void test_cipher() {
    private static final String PROVIDER_NAME = "BC";
    final Provider provider = Security.getProvider(PROVIDER_NAME);

    Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", provider);
}

Довольно простой ответ на ваши объяснения, с использованием только хеш-функций (без шифра):

1 делиться секретом между сервером и клиентом. это должно быть сделано раньше. Любая строка делает работу (вы можете увидеть это как пароль).

У клиента есть P, у сервера есть P

1bis лучшая безопасность: хэш P, с обеих сторон: у клиента и сервера есть Ph

2 соединения:

2a сервер создает случайный R и отправляет клиенту

2b клиент делает Ph x R (например, бит за битом) и хеширует его => (Ph x R)h

и отправляет это

2 c сервер может сделать то же самое: (Ph x R)h и сравнить его

Один недостаток: если вы получаете Ph на сервере, вы можете подделать клиента. Чтобы избежать этого, вы должны использовать другие функции.

Другой вариант (более безопасный, если вы не доверяете серверу): используйте асимметричный ключ и RSA, как в исходном коде.

Вы не должны делать то, что вы пытаетесь сделать. Это не точка шифрования, чтобы иметь возможность сравнивать два зашифрованных значения, чтобы определить, являются ли они одинаковыми. Я уверен, что вы МОЖЕТЕ заставить это работать, но вы фактически отключите функции и сделаете все, что шифруете, менее безопасным, чтобы они отображались одинаково.

Если вы хотите иметь возможность сравнивать два значения без расшифровки пароля, то вам действительно нужна хеш-функция. В частности, обратите внимание на использование как минимум SHA1 или SHA-256 (лучше).

Как хэшировать строку с sha256 в Java?

По своей сути, хеш-код является односторонним (например, вы не можете перевернуть пароль без чего-либо вроде Rainbow Table). Тем не менее, он предназначен для использования именно так, как вы описываете, сравнивая новое значение со старым.

Если вы действительно хотите работать с зашифрованными значениями, вам следует расшифровать значение пароля и сравнить его с обычным текстом. Тем не менее, хеширование следует передовой практике.

Другие вопросы по тегам