【问题标题】:Encryption/Decryption between C# and JavaC#和Java之间的加密/解密
【发布时间】:2018-02-04 12:29:20
【问题描述】:

在 C# 上有一个加密密码的服务器(方法 encryptCSharp),android 应用程序接收加密的密码、salt、passPhrase 和 initVector。 我必须用 Java 解密该密码。 Server-guy 向我发送了 C# 中的 encrypt 方法,我需要创建 encryptJava 和 decryptJava 方法,它们在 Java 中的工作方式与在 C# 中的工作方式相同。 要创建在 Java 中不存在的 PasswordDeriveBytes,我使用此处的示例 Encryption Diff Between Java and C#

那么,我的问题是,我的 Java 方法出了什么问题?两个都不行

更新:我对 sn-ps 做了一些更改,现在一切正常!!

我调用这些方法:

String encryptedText = encryptJava("12345", "100", "@.erf.net34", "@gugnet@gugnet77");//it works!!
String decryptedText = decryptJava(encryptedText, "100", "@.erf.net34", "@gugnet@gugnet77");//it doesn't work!!

这是我使用的 Java 方法和来自服务器的 C# 方法。

C#(我无法更改)

public static String encryptCSharp(String plainText, String saltValue, String passPhrase, String initVector) {
    String hashAlgorithm = "SHA1";
    int passwordIterations = 1;
    int keySize = 256;
    // Convert strings into byte arrays.
    // Let us assume that strings only contain ASCII codes.
    // If strings include Unicode characters, use Unicode, UTF7, or UTF8
    // encoding.
    byte[] initVectorBytes = Encoding.ASCII.GetBytes(initVector);
    byte[] saltValueBytes = Encoding.ASCII.GetBytes(saltValue);

    // Convert our plaintext into a byte array.
    // Let us assume that plaintext contains UTF8-encoded characters.
    byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);

    // First, we must create a password, from which the key will be derived.
    // This password will be generated from the specified passphrase and
    // salt value. The password will be created using the specified hash
    // algorithm. Password creation can be done in several iterations.
    PasswordDeriveBytes password = new PasswordDeriveBytes(
            passPhrase,
            saltValueBytes,
            hashAlgorithm,
            passwordIterations);

    // Use the password to generate pseudo-random bytes for the encryption
    // key. Specify the size of the key in bytes (instead of bits).
    byte[] keyBytes = password.GetBytes(keySize / 8);

    // Create uninitialized Rijndael encryption object.
    RijndaelManaged symmetricKey = new RijndaelManaged();

    // It is reasonable to set encryption mode to Cipher Block Chaining
    // (CBC). Use default options for other symmetric key parameters.
    symmetricKey.Mode = CipherMode.CBC;

    // Generate encryptor from the existing key bytes and initialization
    // vector. Key size will be defined based on the number of the key
    // bytes.
    ICryptoTransform encryptor = symmetricKey.CreateEncryptor(
            keyBytes,
            initVectorBytes);

    // Define memory stream which will be used to hold encrypted data.
    MemoryStream memoryStream = new MemoryStream();

    // Define cryptographic stream (always use Write mode for encryption).
    CryptoStream cryptoStream = new CryptoStream(memoryStream,
            encryptor,
            CryptoStreamMode.Write);
    // Start encrypting.
    cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);

    // Finish encrypting.
    cryptoStream.FlushFinalBlock();

    // Convert our encrypted data from a memory stream into a byte array.
    byte[] cipherTextBytes = memoryStream.ToArray();

    // Close both streams.
    memoryStream.Close();
    cryptoStream.Close();

    // Convert encrypted data into a base64-encoded string.

    String cipherText = Convert.ToBase64String(cipherTextBytes);

    // Return encrypted string.
    return cipherText;
}

Java 加密消息,它可以工作,我得到了与 C# 中的方法相同的结果

private String encryptJava(String plainText, String saltValue, String passPhrase, String initVector) {//working!!!
    String result = "";

    byte[] initVectorBytes = initVector.getBytes(US_ASCII);
    byte[] saltValueBytes = saltValue.getBytes(US_ASCII);
    byte[] plainTextBytes = plainText.getBytes(UTF_8);

    Cipher cipher;
    try {
        final com.gmail.example.PasswordDeriveBytes password = new com.gmail.example.PasswordDeriveBytes(passPhrase, saltValueBytes);
        final byte[] keyBytes = password.getBytes(256 / Byte.SIZE);
        SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");

        cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        try {
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(initVectorBytes));
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        }

        final byte[] ct = cipher.doFinal(plainTextBytes);
        result = Base64.encodeToString(ct, Base64.DEFAULT);//**added this line!** 
        //result = new String(ct, "US-ASCII");**-- deleted this line!** 
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        e.printStackTrace();
    } catch (BadPaddingException e) {
        e.printStackTrace();
    } catch (IllegalBlockSizeException e) {
        e.printStackTrace();
    }
    return result;
}

在 C# 中模拟相同方法的 Java 方法

public class PasswordDeriveBytes {

    private final MessageDigest hash;

    private final byte[] firstToLastDigest;
    private final byte[] outputBuffer;

    private int position = 0;

    public PasswordDeriveBytes(String password, byte[] salt) {
        try {
            this.hash = MessageDigest.getInstance("SHA-1");

            this.hash.update(password.getBytes(UTF_8));
            this.hash.update(salt);
            this.firstToLastDigest = this.hash.digest();

            final int iterations = 1;//**changed from 100**
            for (int i = 1; i < iterations - 1; i++) {
                hash.update(firstToLastDigest);
                hash.digest(firstToLastDigest, 0, firstToLastDigest.length);
            }

            this.outputBuffer = hash.digest(firstToLastDigest);

        } catch (NoSuchAlgorithmException | DigestException e) {
            throw new IllegalStateException("SHA-1 digest should always be available", e);
        }
    }

    public byte[] getBytes(int requested) {
        if (requested < 1) {
            throw new IllegalArgumentException(
                    "You should at least request 1 byte");
        }

        byte[] result = new byte[requested];

        int generated = 0;

        try {
            while (generated < requested) {
                final int outputOffset = position % outputBuffer.length;
                if (outputOffset == 0 && position != 0) {
                    final String counter = String.valueOf(position / outputBuffer.length);
                    hash.update(counter.getBytes(US_ASCII));
                    hash.update(firstToLastDigest);
                    hash.digest(outputBuffer, 0, outputBuffer.length);
                }

                final int left = outputBuffer.length - outputOffset;
                final int required = requested - generated;
                final int copy = Math.min(left, required);

                System.arraycopy(outputBuffer, outputOffset, result, generated, copy);

                generated += copy;
                position += copy;
            }
        } catch (final DigestException e) {
            throw new IllegalStateException(e);
        }
        return result;
    }
}

最后是 Java 方法不起作用并尝试了解我做错了什么

private String decryptJava(String encryptedText, String saltValue, String passPhrase, String initVector) {
    String result = "";
    byte[] initVectorBytes = initVector.getBytes(US_ASCII);
    byte[] saltValueBytes = saltValue.getBytes(US_ASCII);
    byte[] encryptedTexttBytes = Base64.decode(encryptedText, Base64.DEFAULT);
    Cipher cipher;
    try {
        final PasswordDeriveBytes password = new PasswordDeriveBytes(passPhrase, saltValueBytes);
        final byte[] keyBytes = password.getBytes(256 / Byte.SIZE);
        SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");

        cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        try {
            cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(initVectorBytes));
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        }
        final byte[] ct = cipher.doFinal(encryptedTexttBytes);
        //result = Base64.encodeToString(ct, Base64.DEFAULT); - **deleted this line**
        try {
            result = new String(ct, "US-ASCII");//** added this line**
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        e.printStackTrace();
    } catch (BadPaddingException e) {
        e.printStackTrace();
    } catch (IllegalBlockSizeException e) {
        e.printStackTrace();
    }
    return result;
}

【问题讨论】:

    标签: java c# android encryption cryptography


    【解决方案1】:

    PasswordDeriveBytes 永远不会永远要求比底层哈希函数更多的字节。此函数实现 PBKDF1,它不能输出超过该位数(在您的示例中为 SHA-1 为 160)。

    Microsoft 实现允许更多输出,但实现被破坏到了极致(它甚至可能重复输出!)。使用Rfc2898DeriveBytes 代替它实现了 PBKDF2,也可以在 Java 中使用。使用更大的散列,PBKDF2 可以生成比散列输出更多的字节,但只是以安全为代价。

    【讨论】:

    • 我没有密码学经验,所以我试着理解你的答案。你的意思是使用微软服务器的人必须将方法从PasswordDeriveBytes更改为Rfc2898DeriveBytes
    • 是的,这当然是可取的。 PasswordDeriveBytes 已被 Rfc2898DeriveBytes 取代。据我所知,没有人完全描述过早期使用的扩展算法(它可能包括一些可怕的缓冲区溢出)。
    【解决方案2】:

    最后我找到了一种编码和解码方法的解决方案: (为了不增加这里的代码量,我在上面的sn-ps中做了修改)

    encryptJava方法中我改了一行,

    PasswordDeriveBytes 中,我将迭代次数从 100 更改为 1

    decryptJava方法中我添加了一行,删除了一行。

    【讨论】:

    • 很高兴你让它工作了,但请注意,通过使用 PasswordDeriveBytes 和 1 作为迭代计数,你实际上并没有使它 secure,我希望这是你的之后。
    • @MaartenBodewes,服务器加密方法超出我的能力范围,但我会将您的笔记显示给服务器人员以解决该问题。即使我自己找到了这个特定问题的答案,如果没有你的 Java 方法 - PasswordDeriveBytes,解决它是不可能的。非常感谢您的回答!
    猜你喜欢
    • 1970-01-01
    • 2012-07-12
    • 2014-04-19
    • 1970-01-01
    • 2011-03-26
    • 2023-03-23
    • 2012-02-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多