Я реализую в Java связь с сторонним приложением. Как часть процесса входа в систему, стороннее приложение отправляет зашифрованную строку, которую я должен декодировать и отправлять обратно. Я потратил почти 2 дня на googeling и чтение сообщений, но я не могу найти правильный способ реализовать это.
У меня есть тестовый пример, где зашифрованная строка "c1W2YO1vYQzu6czteEidrG0U4g5gT4h57vAlP7tdjcY =", которая расшифровывается с паролем "GAT", должна возвращать "101714994".
В документации, в которой я указываю это: строка авторизации была зашифрована со следующими настройками:
Это пример С#, как расшифровать строку авторизации:
/// <summary>
/// Decrypts a string.
/// </summary>
/// <param name="content">The string to decrypt.</param>
/// <param name="password">The password to use.</param>
/// <returns>The decrypted string.</returns>
private static string DecryptString(string content, string password) {
Rijndael aes;
byte[] retVal = null;
byte[] contentBytes;
byte[] passwordBytes;
byte[] ivBytes;
try {
//Get the content as byte[]
contentBytes = Convert.FromBase64String(content);
//Create the password and initial vector bytes
passwordBytes = new byte[32];
ivBytes = new byte[16];
Array.Copy(Encoding.Unicode.GetBytes(password), passwordBytes, Encoding.Unicode.GetBytes(password).Length);
Array.Copy(passwordBytes, ivBytes, 16);
//Create the cryptograpy
object aes = Rijndael.Create();
aes.Key = passwordBytes;
aes.IV = ivBytes;
aes.Padding = PaddingMode.PKCS7;
//Decrypt
retVal = aes.CreateDecryptor().TransformFinalBlock(contentBytes, 0, contentBytes.Length);
}
catch {
}
aes = null;
contentBytes = null;
passwordBytes = null;
ivBytes = null;
return Encoding.Unicode.GetString(retVal)
}
Это моя Java-процедура для расшифровки строки:
private String decryptAuthorizationString(String authString, String password) {
try {
//Force the test string
authString = "c1W2YO1vYQzu6czteEidrG0U4g5gT4h57vAlP7tdjcY=";
//Force the test password
password = "GAT";
//Create the password and initial vector bytes
byte[] passwordBytes= new byte[32];
byte[] b= password.getBytes("UTF-8");
int len= b.length;
if (len > passwordBytes.length) len = passwordBytes.length;
System.arraycopy(b, 0, passwordBytes, 0, len);
byte[] ivBytes= new byte[16];
System.arraycopy(passwordBytes, 0, ivBytes, 0, 16);
//Get the authString as byte[]
byte[] authBytes = new BASE64Decoder().decodeBuffer(authString);
InputStream inputStream = new ByteArrayInputStream(authBytes);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// If you have Bouncycastle library installed, you can use
// Rijndael/CBC/PKCS7PADDING directly.
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
Cipher cipher = Cipher.getInstance("Rijndael/CBC/PKCS7PADDING", "BC");
// convertedSecureString and initVector must be byte[] with correct length
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(passwordBytes, "AES"), new IvParameterSpec(ivBytes));
CipherInputStream cryptoStream = new CipherInputStream(inputStream, cipher);
byte[] buffer = new byte[1024];
len = cryptoStream.read(buffer, 0, buffer.length);
while (len > 0) {
outputStream.write(buffer, 0, len);
len = cryptoStream.read(buffer, 0, buffer.length);
}
outputStream.flush();
cryptoStream.close();
String resStr = outputStream.toString("UTF-8");
return resStr; //<<--- resStr must be "101714994"
} catch (Throwable t) {
}
return null;
}
Процедура выполняется без ошибок, но результат не тот, в чем я нуждаюсь. Любая помощь будет очень оценена.
Для этого вам не нужен BouncyCastleProvider, поскольку AES уже включен в Java. Однако заполнение PKCS # 7 неправильно указано "PKCS5Padding"
, поэтому "AES/CBC/PKCS7Padding"
нельзя указать без Bouncy Castle.
Unicode
умолчанию для.NET на самом деле более совместима с UTF-16LE. Оставьте это Microsoft, чтобы не придерживаться стандартных имен (хотя они, возможно, предшествовали ей).
Java JCE на самом деле не строит потоки, как классы С#, поэтому лучше избегать потоковой передачи.
Я переписал ваш пример кода, чтобы показать, как правильно его кодировать на Java (вам нужно быть совместимым с Java 7, хотя). Не перетаскивайте исключения из таблицы, превращайте их в AssertError
или RuntimeException
s.
Я использую декодер Bouncy Castle Base 64, так как он был доступен как для вас, так и для меня (но в остальном это не зависит от Bouncy). Java 8 имеет базовый класс 64.
Так что без дальнейших церемоний:
import static java.nio.charset.StandardCharsets.UTF_16LE;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.util.encoders.Base64;
public class AuthenticationStringDecrypter {
private static final String AES_CBC_PKCS5PADDING = "AES/CBC/PKCS5PADDING";
private static final int KEY_SIZE = 256;
public static void main(final String[] args) throws Exception {
System.out.println(decryptAuthorizationString(
"c1W2YO1vYQzu6czteEidrG0U4g5gT4h57vAlP7tdjcY=", "GAT"));
}
private static String decryptAuthorizationString(final String authString,
final String password) {
try {
// --- check if AES-256 is available
if (Cipher.getMaxAllowedKeyLength(AES_CBC_PKCS5PADDING) < KEY_SIZE) {
throw new IllegalStateException("Unlimited crypto files not present in this JRE");
}
// --- create cipher
final Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5PADDING);
// --- create the key and initial vector bytes
final byte[] passwordEncoded = password.getBytes(UTF_16LE);
final byte[] keyData = Arrays.copyOf(passwordEncoded, KEY_SIZE
/ Byte.SIZE);
final byte[] ivBytes = Arrays.copyOf(keyData, cipher.getBlockSize());
// --- init cipher
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyData, "AES"),
new IvParameterSpec(ivBytes));
// --- decode & decrypt authentication string
final byte[] authBytes = Base64.decode(authString);
final byte[] decryptedData = cipher.doFinal(authBytes);
// WARNING: may still decrypt to wrong string if
// authString or password are incorrect -
// BadPaddingException may *not* be thrown
return new String(decryptedData, UTF_16LE);
} catch (BadPaddingException | IllegalBlockSizeException e) {
// failure to authenticate
return null;
} catch (final GeneralSecurityException e) {
throw new IllegalStateException(
"Algorithms or unlimited crypto files not available", e);
}
}
}
sun.misc.BASE64Decoder
. Это внутреннее и не поддерживается .