简体   繁体   English

Java解密加密兼容SJCL?

[英]Java decryption and encryption compatible with SJCL?

I need to encrypt & decrypt data with both Java (on Android) and SJCL (I could plausibly switch to another JS crypto library, but am familiar with SJCL so would prefer to stick with it if possible).我需要使用 Java(在 Android 上)和SJCL加密和解密数据(我可以合理地切换到另一个 JS 加密库,但我对 SJCL 很熟悉,所以如果可能的话,我宁愿坚持使用它)。

I have the SJCL end working fine, but at the Java end I'm not really sure what parameters I need to use to set up the key generator and cipher.我的 SJCL 端工作正常,但在 Java 端我不确定我需要使用哪些参数来设置密钥生成器和密码。 The code I have so far for decryption is:到目前为止,我用于解密的代码是:

SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 1024, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
String plaintext = new String(cipher.doFinal(ciphertext), "UTF-8");
return plaintext;

Where salt, iv and ciphertext are extracted as strings from the JSON object produced by SJCL and then decoded using a Base64 decoder to byte arrays.其中 salt、iv 和密文从 SJCL 生成的 JSON 对象中提取为字符串,然后使用 Base64 解码器解码为字节数组。

Unfortunately, I have a few problems with this and the code above doesn't work.不幸的是,我对此有一些问题,上面的代码不起作用。

The first problem I have is that PBKDF2WithHmacSHA256 doesn't seem to be a recognised key generation algorithm.我遇到的第一个问题是 PBKDF2WithHmacSHA256 似乎不是公认的密钥生成算法。 I'm not entirely sure that this is what I want, but it appears to be right based on reading the SJCL documentation?我不完全确定这是我想要的,但基于阅读 SJCL 文档似乎是正确的? Java does recognise PBKDF2WithHmacSHA1, but this doesn't seem to be the same algorithm SJCL implements. Java 确实识别 PBKDF2WithHmacSHA1,但这似乎与 SJCL 实现的算法不同。

Secondly, if I try using the SHA1 key algorithm, I get an error about invalid key size.其次,如果我尝试使用 SHA1 密钥算法,我会收到关于无效密钥大小的错误。 Do I need to install something to enable AES with 256-bit keys?我是否需要安装一些东西才能使用 256 位密钥启用 AES? Telling the key factory to produce a 128-bit key works OK (although obviously is not compatible with SJCL, which is using a 256-bit key).告诉密钥工厂生产 128 位密钥工作正常(尽管显然与使用 256 位密钥的 SJCL 不兼容)。

Thirdly, what cipher mode should I be using?第三,我应该使用什么密码模式? I'm pretty sure CBC isn't right... SJCL's documentation mentions both CCM and OCB, but Java doesn't seem to support either of these out of the box -- again, do I need to install something to make this work?我很确定 CBC 是不正确的...... SJCL 的文档同时提到了 CCM 和 OCB,但 Java 似乎不支持这些中的任何一个——同样,我是否需要安装一些东西来使这项工作? And which one does SJCL default to? SJCL 默认为哪一个?

And finally, even if I pick parameters that make Java not complain about missing algorithms, it complains that the IV provided by decoding the SJCL output is the wrong length, which it certainly appears to be: there are 17 bytes in the resulting output, not 16 as is apparently required by AES.最后,即使我选择了使 Java 不会抱怨缺少算法的参数,它也会抱怨通过解码 SJCL 输出提供的 IV 长度错误,这肯定是:结果输出中有 17 个字节,而不是16 显然是 AES 要求的。 Do I just ignore the last byte?我只是忽略最后一个字节吗?

I haven't tried it (in the end I switched away from using Javascript crypto in favour of using an embedded java applet with bouncycastle to handle communication), but GnuCrypto (a bouncycastle fork) supports PBKDFWithHmacSHA256.我还没有尝试过(最后我不再使用 Javascript 加密,转而使用带有 bouncycastle 的嵌入式 Java 小程序来处理通信),但 GnuCrypto(一个 bouncycastle 分支)支持 PBKDFWithHmacSHA256。 The fixed character encoding handling in SJCL presumably fixes the unexpected length of the IV (?), so this would just leave the cipher mode. SJCL 中的固定字符编码处理大概修复了 IV (?) 的意外长度,因此这只会离开密码模式。 From this point, it appears that the easiest approach would be to implement a relatively simple cipher mode (eg CTR) as an add-on for SJCL, which ought to be only a few hours work even for someone unfamiliar with the code, after which it is simply a matter of encoding and decoding the JSON-encoded data packets that are used by SJCL (which ought to be trivial).从这一点来看,似乎最简单的方法是实现一个相对简单的密码模式(例如 CTR)作为 SJCL 的附加组件,即使对于不熟悉代码的人来说,这也应该只需要几个小时,然后这只是对 SJCL 使用的 JSON 编码数据包进行编码和解码的问题(这应该是微不足道的)。

As an alternative, it would certainly be possible to implement OCB mode for Java, despite the fact that the algorithm is proprietary, as there is a public patent grant for software distributed under the GPL (http://www.cs.ucdavis.edu/~rogaway/ocb/grant.htm).作为替代方案,当然可以为 Java 实现 OCB 模式,尽管该算法是专有的,因为在 GPL (http://www.cs.ucdavis.edu) 下分发的软件有公共专利授权/~rogaway/ocb/grant.htm)。

Interestingly, I wonder whether GnuCrypto would accept a patch for OCB mode support?有趣的是,我想知道 GnuCrypto 是否会接受 OCB 模式支持的补丁? GnuCrypto is distributed under GPL-with-libraries-exemption, which would appear to qualify as "any version of the GNU General Public License as published by the Free Software Foundation", so theoretically at least this should be possible. GnuCrypto 是在 GPL-with-libraries-exemption 下分发的,这似乎符合“自由软件基金会发布的 GNU 通用公共许可证的任何版本”,所以理论上至少这应该是可能的。

SJCL AES in java Java中的SJCL AES

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.json.JSONObject;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.Base64.*;
import java.util.HashMap;
import java.util.Map;

import static java.nio.charset.StandardCharsets.UTF_8;

/**
 *
 * SJCL 1.0.8
 *
 * dependencies:
 * compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.64'
 * compile group: 'org.json', name: 'json', version: '20190722'
 *
 * ref: https://blog.degering.name/posts/java-sjcl
 */
public class AesSJCL {
    // Simply prints out the decoded string.
    public static void main(String[] args) throws Exception {
        String password = "password";
        String plainText = "Who am I?";

        // encryption
        Map<String, Object> result = new AesSJCL().encrypt( password, plainText);
        String json =  new JSONObject(result).toString();
        System.out.printf("encrypted output:\n%s\n", json);

        System.out.printf("\njavascript testing code:\nsjcl.decrypt(\"%s\", '%s')\n", password, json);

        // decryption
        String decryptedText = new AesSJCL().decrypt(password, json);
        System.out.printf("\ndecrypted output: \n%s\n", decryptedText);
    }

    /**
     *
     * @param password  - password
     * @param encryptedText - {"cipher":"aes","mode":"ccm","ct":"r7U/Gp2r8LVNQR7kl5qLNd8=","salt":"VwSOS3jCn6M=","v":1,"ks":128,"iter":10000,"iv":"5OEwQPtHK2ej1mHwvOf57A==","adata":"","ts":64}
     * @return
     * @throws Exception
     */
    public String decrypt(String password, String encryptedText) throws Exception {
        Decoder d = Base64.getDecoder();

        // Decode the encoded JSON and create a JSON Object from it
        JSONObject j = new JSONObject(new String(encryptedText));

        // We need the salt, the IV and the cipher text;
        // all of them need to be Base64 decoded
        byte[] salt=d.decode(j.getString("salt"));
        byte[] iv=d.decode(j.getString("iv"));
        byte[] cipherText=d.decode(j.getString("ct"));

        // Also, we need the keySize and the iteration count
        int keySize = j.getInt("ks"), iterations = j.getInt("iter");

        // https://github.com/bitwiseshiftleft/sjcl/blob/master/core/ccm.js#L60
        int lol = 2;
        if (cipherText.length >= 1<<16) lol++;
        if (cipherText.length >= 1<<24) lol++;

        // Cut the IV to the appropriate length, which is 15 - L
        iv = Arrays.copyOf(iv, 15-lol);

        // Crypto stuff.
        // First, we need the secret AES key,
        // which is generated from password and salt
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        KeySpec spec = new PBEKeySpec(password.toCharArray(),
                salt, iterations, keySize);
        SecretKey tmp = factory.generateSecret(spec);
        SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");

        // Now it's time to decrypt.
        Cipher cipher = Cipher.getInstance("AES/CCM/NoPadding",
                new BouncyCastleProvider());
        cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));

        // Return the final result after converting it to a string.
        return new String(cipher.doFinal(cipherText));
    }


    /**
     *
     * @param password
     * @param plainText
     * @return
     * @throws Exception
     */
    public Map<String, Object> encrypt(String password, String plainText) throws Exception {
        int iterations = 10000;  // default in SJCL
        int keySize = 128;

        // https://github.com/bitwiseshiftleft/sjcl/blob/master/core/convenience.js#L321
        // default salt bytes are 8 bytes
        SecureRandom sr = SecureRandom.getInstanceStrong();
        byte[] salt = new byte[8];
        sr.nextBytes(salt);

        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, keySize);
        SecretKey tmp = factory.generateSecret(spec);
        SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");

        // https://github.com/bitwiseshiftleft/sjcl/blob/master/core/random.js#L87
        // default iv bytes are 16 bytes
        SecureRandom randomSecureRandom = SecureRandom.getInstanceStrong();
        byte[] iv = new byte[16];
        randomSecureRandom.nextBytes(iv);

        int ivl = iv.length;
        if (ivl < 7) {
            throw new RuntimeException("ccm: iv must be at least 7 bytes");
        }

        // compute the length of the length
        int ol=plainText.length();
        int L=2;
        for (; L<4 && ( ol >>> 8*L ) > 0; L++) {}
        if (L < 15 - ivl) { L = 15-ivl; }

        byte[] shortIV = Arrays.copyOf(iv, 15-L);

        // Now it's time to decrypt.
        Cipher cipher = Cipher.getInstance("AES/CCM/NoPadding", new BouncyCastleProvider());
        cipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(shortIV));

        byte[] encryptedBytes = cipher.doFinal(plainText.getBytes(UTF_8));

        Encoder encoder = Base64.getEncoder();

        Map<String, Object> map = new HashMap<>();
        map.put("iv", encoder.encodeToString(iv));
        map.put("iter", iterations);
        map.put("ks", keySize);
        map.put("salt", encoder.encodeToString(salt));
        map.put("ct", encoder.encodeToString(encryptedBytes));
        map.put("cipher", "aes");
        map.put("mode", "ccm");
        map.put("adata", "");
        map.put("v", 1);   // I don't know what it is.
        map.put("ts", 64);  // I don't know what it is.

        return map;
    }
}

github gist by me 我的 github 要点

ref: Java talks SJCL参考: Java 谈 SJCL

You may have to use BouncyCastle to get all the cryptographic features used in SJCL.您可能必须使用 BouncyCastle 来获取 SJCL 中使用的所有加密功能。 Make sure you're base64 decoding everything correctly and that SJCL doesn't add in length indicators or similar.确保您正确地对所有内容进行 base64 解码,并且 SJCL 没有添加长度指示符或类似内容。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM