所以我一直坚持使用我一直在设计的聊天程序这个非常讨厌的问题。客户端以AES256格式加密消息,将它们作为字符串发送到服务器,然后服务器以下列形式将它们重新分配给客户端:
<encrypted username> encrypted message
客户端的工作是加密消息并解密它们。从服务器返回的字符串与发送的字符串匹配。
ENCRYPTOR和DECRYPTOR在使用加密的字符串 LOCALLY时正常工作。将未在同一实例中加密的加密邮件放入DECRYPTOR会产生空指针异常,BadPadding异常或其他奇怪错误。
聊天系统运行良好,但当消息需要解密
时会出现问题所以,我搞乱了代码并尝试了很多变通方法来查看数据是否会再次被解密。我最终尝试了多种解密方法,看看其他人是否会工作,但无济于事。同样的问题。
int port=1234;
String host="localhost";
try
{
socket=new Socket(host,port);
userip=new BufferedReader(new InputStreamReader(System.in));
output=new PrintStream(socket.getOutputStream());
input=new BufferedReader(new InputStreamReader(socket.getInputStream()));
// AES256 encryptedmessage = new AES256();
}
catch(Exception e)
{
System.err.println("Couldn't connect. "+host);
}
if(socket!=null)
{
try
{
//AES256 message = new AES256();
String encrypted = "";
new Thread(new Client()).start();
while(!flag)
{
encrypted = Global.encryptor.encrypt(userip.readLine());
//encrypted = message.encrypt(userip.readLine());
output.println(encrypted);
System.out.println(encrypted);
//NOT NECESSARY ^^^
}
output.close();
input.close();
socket.close();
}
将输入发送到服务器的部分是
output.println(encrypted);
其中encrypted是已通过加密方法的字符串 在AES256.java中 (我尝试在自己的文件中声明一个AES256对象,试图避免空指针错误/其他错误再次出现。它的工作方式完全相同并导致相同的错误。)
客户端正确接收用户的输入,加密并将其发送到服务器。
SERVER 通过以下代码接收用户的加密用户名和消息:
public void run()
{
//AES256 X = new AES256();
String msg;
String username;
try
{
input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
output = new PrintStream(socket.getOutputStream());
output.println("Enter your alias: ");
username = input.readLine();
//THIS IS IMPORTANT.
output.println("CONNECTION ESTABLISHED");
output.println(username + ": CONNECTED.");
output.println("To disconnect, enter $$");
output.println("END UNENC");
for (int i = 0; i <= 9; i++)
if (th[i] != null && th[i] != this)
th[i].output.println("------------A new user connected: " + username);
while (true)
{
msg = input.readLine();
if (msg.startsWith("$$"))
break;
for (int i = 0; i <= 9; i++)
if (th[i] != null){
th[i].output.println("<" + username + "> " + msg);
}
}
这里的相关代码是
for (int i = 0; i <= 9; i++)
if (th[i] != null){
th[i].output.println("<" + username + "> " + msg);
}
}
上述代码包含客户端提供的用户名和消息,并将其发送给所有已连接的用户。用户名和邮件仍然是加密。
此时,客户收到了这种性质的东西:
<nf8q3nfqlidcnalkm2> djiami2j389danfill23nlfuinvavv34
用户名和消息与最初发送到服务器的用户名和消息完全相同。但是,当我尝试解密这两件事时,错误就会发挥作用(主要是空指针错误)。
此信息通过此代码返回客户端:
public void run()
{
String msg;
String unencrypt = "";
String line = "";
String username = "";
String message = "";
int index1 = 0;
int index2 = 0;
try
{
while((msg=input.readLine())!=null)
{
// d.SetMessage(msg);
// String original = msg;
// byte[] utf8Bytes = original.getBytes("UTF-8");
if(!msg.contains("<")){
System.out.println(msg);
}
else if(msg.contains("<"))
{
username = msg.substring(1, msg.indexOf(">"));
System.out.println("USERNAME:"+username);
message = msg.substring(msg.indexOf(">")+2,msg.length());
System.out.println("MESSAGE:"+message);
index1 = line.indexOf('<');
index2 = line.indexOf('>');
unencrypt = Global.encryptor.decrypt(username);
//System.out.println("<" + d.decrypt(d.GetMessage() + ">"));
}
}
flag=true;
}
catch(IOException e)
{
System.err.println("IOException" + e);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
你会注意到很多未使用的,冗余的或未完成的代码,但这仅仅是因为我已经重复了千种不同的方法。当客户端尝试解密从服务器 解析字符串时,此特定实例中的错误就会出现。
请记住,当字符串直接从加密器返回到同一实例中的解密器时,加密器和解密器可以正常工作。
主要的罪魁祸首是 ALL 问题的原因是当来自服务器的这个字符串试图以任何方式解密时。这种方法只是其中一种方式:
unencrypt = Global.encryptor.decrypt(username);
让我们看看它给出的错误,无论何时使用来自服务器的字符串运行此解密器:
javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:966)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:824)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:436)
at javax.crypto.Cipher.doFinal(Cipher.java:2165)
at Messenger.AES256.decrypt(AES256.java:84)
at Messenger.Client.run(Client.java:106)
at java.lang.Thread.run(Unknown Source)
java.lang.NullPointerException
at java.lang.String.<init>(Unknown Source)
at Messenger.AES256.decrypt(AES256.java:91)
at Messenger.Client.run(Client.java:106)
at java.lang.Thread.run(Unknown Source)
我对如何解决这个问题一直毫无头绪。以下是AES256的内容:
ENCRYPTOR(发送到服务器的内容)
public String encrypt(String plainText) throws Exception {
//get salt
salt = generateSalt();
byte[] saltBytes = salt.getBytes("UTF-8");
// Derive the key
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec spec = new PBEKeySpec(
password.toCharArray(),
saltBytes,
pswdIterations,
keySize
);
SecretKey secretKey = factory.generateSecret(spec);
SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");
//encrypt the message
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
ivBytes = params.getParameterSpec(IvParameterSpec.class).getIV();
byte[] encryptedTextBytes = cipher.doFinal(plainText.getBytes("UTF-8"));
return new Base64().encodeAsString(encryptedTextBytes);
}
DECRYPTOR(在发送到客户端的字符串上运行的内容)
@SuppressWarnings("static-access")
public String decrypt(String encryptedText) throws Exception {
byte[] saltBytes = salt.getBytes("UTF-8");
byte[] encryptedTextBytes = new Base64().decodeBase64(encryptedText);
// Derive the key
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec spec = new PBEKeySpec(
password.toCharArray(),
saltBytes,
pswdIterations,
keySize
);
SecretKey secretKey = factory.generateSecret(spec);
SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");
// Decrypt the message
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(ivBytes));
byte[] decryptedTextBytes = null;
try {
decryptedTextBytes = cipher.doFinal(encryptedTextBytes);
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return new String(decryptedTextBytes);
}
我的信念是,当字符串被发送到服务器并返回时,字符串会发生变化。或者,AES256类需要在加密后记住字符串。我不确定。
有人可以就此事给我一些见解吗?我觉得它非常接近工作,但是一旦它们被送回客户端,我就无法解密这些字符串。
编辑:
final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for ( int j = 0; j < bytes.length; j++ ) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);}
我正在尝试将ivBytes和salt转换为十六进制,以便我可以将它们发送到服务器并返回并获得我需要再次解密的内容。
当我尝试打印这个新的十六进制时,我得到:
HEXADECIMAL IV BYTES: ?T???/0??q"?H
编辑#2:
我已成功将ivBytes []和saltBytes []转换为十六进制格式。我以...的形式向服务器发送输入。
HexIvBytes + " " + HexSaltBytes + " " + EncryptedMessage
以下是输出的示例:
testing
HEXADECIMAL IV BYTES: 70FDE9D7D1A0E2AAD92A95E113186067
IV BYTES: [B@2aafb23c
HEXADECIMAL SALT BYTES: 71C385C387C2ACC2BDC3B2CB9C1BC3926F63370749C3B4C2BD04E280B9C2A90E
SALT BYTES: [B@2b80d80f
ENCRYPTED MESSAGE:eYgv/GxKP3oG3SbbnflTyw==
IVBYTES:[B@2aafb23c
<6C5D1C71F8C775C363C914982C617B11 C386C3A5C3A4C3AE1942C5BE565268175C5A7354C39F6F15C5A1C2A7 NtDi9/P696DgTezN7T0ZVg==> 70FDE9D7D1A0E2AAD92A95E113186067 71C385C387C2ACC2BDC3B2CB9C1BC3926F63370749C3B4C2BD04E280B9C2A90E eYgv/GxKP3oG3SbbnflTyw==
所以我现在试图在客户端解密这些东西。这就是我正在使用的:
if(msg.contains("<"))
{
username = msg.substring(1, msg.indexOf(">"));
usernameivHex = username.substring(0, username.indexOf(" "));
saltHex = username.substring(username.indexOf(" "), username.indexOf("|"));
encryptedUsername = username.substring(username.indexOf("|"),username.indexOf(">"));
d.setivBytes(hexStringToByteArray(usernameivHex));
d.setSaltBytes(hexStringToByteArray(saltHex));
System.out.println("DECRYPYTED USERNAME: " + d.decrypt(encryptedUsername));
}
}
虽然我在这条线上遇到了巨大的界限错误:
encryptedUsername = username.substring(username.indexOf("|"),username.indexOf(">"));
java.lang.StringIndexOutOfBoundsException: String index out of range: -100
at java.lang.String.substring(Unknown Source)
at Messenger.Client.run(Client.java:155)
at java.lang.Thread.run(Unknown Source)
编辑#3:
成功!使用传输的IvBytes和SaltBytes,我能够在客户端成功解密字符串。
答案 0 :(得分:1)
您需要使用相同的盐,而不仅仅是相同的密码。密码和盐确定密钥种子的值 - PBKDF2的输出。这又是密钥的输入材料(有时只需使用密钥种子中正确的字节数)。
如果您的密钥无效,那么很可能最终会出现填充错误。如果您想确保错误的盐/密码出错,可能需要包含身份验证标记(例如使用HMAC生成)。
发送salt的最简单方法是在编码之前将其添加到密文之前。如果要分离salt和密文,可以将它们作为JSON string : value
对发送
"salt"=<base64>
"ciphertext"=<base64>
IV也是如此。