Почему шифрование Java Cipher с одинаковой строкой, ключом и SecureRandom всегда отличается?
Спасибо за комментарии от всех.
Однако я должен объяснить этот вопрос.
Я знаю, что мы не должны сравнивать результат шифрования с использованием генератора случайных ошибок, поскольку это может снизить безопасность. Тем не менее, я просто хочу сделать это в тестировании, и я буду использовать оригинальный механизм случайных в реальной работе.
Дело в том, что мне нужно войти на сервер с учетной записью / паролем, выполнив следующие действия:
- Получить информацию с сервера: abcom / get_cipher.cgi.
- получить ответ, разобрать его и получить некоторую информацию для создания шифра.
- используйте шифр для шифрования учетной записи / пароля, а затем составьте следующий 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). Тем не менее, он предназначен для использования именно так, как вы описываете, сравнивая новое значение со старым.
Если вы действительно хотите работать с зашифрованными значениями, вам следует расшифровать значение пароля и сравнить его с обычным текстом. Тем не менее, хеширование следует передовой практике.