用JavaScript加密,用Java解密。

3
我在我的React Native应用中使用AES加密,如下所示。
import CryptoJS from 'crypto-js' ;
encryptFun() {
    var data = "123456";
    var key  = CryptoJS.enc.Latin1.parse('1234567812345678');
    var iv   = CryptoJS.enc.Latin1.parse('1234567812345678');  
    var encrypted = CryptoJS.AES.encrypt(
      data,
      key,
      {iv:iv,mode:CryptoJS.mode.CBC,padding:CryptoJS.pad.ZeroPadding
    });
    console.log('encrypted: ' + encrypted) ;
    var decrypted = CryptoJS.AES.decrypt(encrypted,key,{iv:iv,padding:CryptoJS.pad.ZeroPadding});
    console.log('decrypted: '+decrypted.toString(CryptoJS.enc.Utf8));
  }

输出结果= 加密后的数据: aK7+UX24ttBgfTnAndz9aQ==

以下是我在Java后端使用的代码以获取解密结果:

 public static String desEncrypt() throws Exception {

        try
        {
            String data = "aK7+UX24ttBgfTnAndz9aQ==" ;
            String key = "1234567812345678";
            String iv = "1234567812345678";

            byte[] encrypted1 = new BASE64Decoder().decodeBuffer(data);

            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
            SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
            IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());

            cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);

            byte[] original = cipher.doFinal(encrypted1);
            String originalString = new String(original);
            return originalString;
        }
        catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

输出 = 解密 : = 123456[][][][][][][][][][]

我得到的输出如上所示,是16位的。我想要的是输出仅为123456。


什么是BASE64Decoder类? - azro
可能是[如何在Javascript中使用AES CBC零填充加密并在Java中解密]的重复问题(https://stackoverflow.com/questions/55795767/how-encrypt-with-aes-cbc-zero-padding-in-javascript-and-decrypt-with-java)。 - azro
1
附注:在Java中调用String::getBytes时,应指定编码 - 如果不这样做,您的解决方案将依赖于使用的默认字符集。 - Michał Krzywański
我在这里找到了一个好的解决方案:https://dev59.com/J2sz5IYBdhLWcg3wmpBa#21252990 - Omkar T
3个回答

2
我建议您使用java.util.Base64进行解码。以下内容是正确的。我还建议在return originalString中使用修剪并查看其是否有效。
public class Decrypt {

    public static void main(String[] args) {
        try
        {
            String data = "aK7+UX24ttBgfTnAndz9aQ==" ;
            String key = "1234567812345678";
            String iv = "1234567812345678";

            Decoder decoder = Base64.getDecoder();   
             byte[] encrypted1 = decoder.decode(data);

            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
            SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
            IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());

            cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);

            byte[] original = cipher.doFinal(encrypted1);
            String originalString = new String(original);
            System.out.println(originalString.trim());
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}


执行 System.out.println(originalString + "<<");,你就会看到幽灵 ;) - azro
谢谢指出这个问题 :)。我已经添加了一个trim()函数来解决它。 - Alan Lal

0

由于您在JS中使用了zeros填充,因此无法使用NoPadding进行解密,解决方案是对数组或字符串进行操作。

  • 修剪数组

    int i = original.length - 1;
    while (i >= 0 && original[i] == 0) {
        --i;
    }
    return new String(Arrays.copyOf(original, i + 1));
    
  • 修剪字符串

    return new String(original).trim();
    
  • 手动删除零值

    return new String(original).replace("\0", "");
    

或者,由于Java中似乎没有实现ZeroPAdding({{link1:Cipher Documentation}}),可以参考{{link2:如何使用AES CBC Zero Padding在Javascript中加密并在Java中解密}},建议使用CryptoJS.pad.Pkcs


我认为在将字节数组转换为字符串之前,最好先删除填充的零字节。 - tibetty

0

控制器部分生成RSA密钥(公钥和私钥)

package com.secure.encryption.decryption.controller;

import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.secure.encryption.decryption.rsa.keystore.KeyStore;
import com.secure.encryption.decryption.rsa.service.RSAKeyPairGenerator;
import com.secure.encryption.decryption.util.AesUtil;


@RestController
public class EncryptDecryptController {

    
    @Autowired
    RSAKeyPairGenerator rsa;
    
    @Autowired
    KeyStore keystore;
    
    
    @CrossOrigin
    @GetMapping(value = "get/rsa/public")
    public Map<String, Object> generateRSA_PUBLIC_PRIVATE_KEY()
    {
        Map<String, Object> response = new HashMap<>();
        /**
         * Below function creates rsa public & private key
         * While this api only share PUBLICKEY
         * Whereas PRIVATEKEY is stored in bean/model
         */
        String public_KEY = null;
        public_KEY = rsa.KeyPairGenerator();
        response.put("public", public_KEY);
        return response;
    }
    
    @CrossOrigin
    @PostMapping(value = "/decrypt/AES_encyptedKEY/with/RSA_privateKEY/decryptdata/with/aes")
    public Map<String, Object> decrypt(@RequestBody Map<String, Object> req) throws UnsupportedEncodingException
    {
        /**
         * First decrypt AES-Key which is encrypted with RSA Public Key
         * Use RSA privateKey for decryption
         */
        String Decrypted_AES_Key = rsa.decrypt(req.get("phrase").toString(),keystore.getPrivateKey());
        
        byte[] decoded = Base64.getDecoder().decode(req.get("data").toString());
        String encryptedAES_Data = new String(decoded, StandardCharsets.UTF_8); 
        
        /**
         * Decode data to base 64
         * Use AES key to decrypt the data
         */
        AesUtil aesUtil = new AesUtil(128, 1000);
        String decryptedAES_Data = aesUtil.decrypt(
                req.get("salt").toString(),
                req.get("iv").toString(), 
                Decrypted_AES_Key,
                encryptedAES_Data);
        
        /**
         * Map actual data as response
         */
        req.put("data", decryptedAES_Data);
    
        return req;
    }
    
    @CrossOrigin
    @GetMapping(value = "/decryptfrom/backend/aes/plain/decrypt/frontend")
    public Map<String, Object> sendAESencryptedData()
    {
        /**
         * Generate random key  
         * Encrypt data using same
         * pass key to UI
         * Decrypt using same key, iv and salt
         */
        Map<String, Object> response = new HashMap<>();
        int i = (int) (new Date().getTime()/1000);
        //String iv = generateIv().toString();
        AesUtil aesUtil = new AesUtil(128, 1000);
        String phrase = String.valueOf(i);//"my secret key 123";
        //String salt =  new String(generateSalt(32));
        String iv = "bb6a69ace7a11a38fba164238e000c7c";
        String salt = "6c3674b6469467ab0b9f2b57ce36e78d";
        String encryptedAES_Data = 
                Base64.getEncoder().encodeToString(
                aesUtil.encrypt(
                salt,
                iv, 
                phrase,
                "ganesha")
                );
        
        response.put("data", encryptedAES_Data);
        response.put("salt", salt);
        response.put("iv", iv);
        response.put("key", phrase);
        return response;
    }
    
    /*
    public IvParameterSpec generateIv() {
        byte[] iv = new byte[16];
        new SecureRandom().nextBytes(iv);
        return new IvParameterSpec(iv);
    }
    
    private byte[] generateSalt(int size) {
        try {
             byte[] salt = new byte[size];
                SecureRandom rand = SecureRandom.getInstance("SHA1PRNG");
                rand.nextBytes(salt);

                return salt;
        } catch (Exception e) {
            System.err.println(e);
        }
        return null;
       
    }
    */
    
}

生成RSA随机密钥的服务

package com.secure.encryption.decryption.rsa.service;

public interface RSAKeyPairGenerator {

    public String KeyPairGenerator();
    public byte[] encrypt(String data, String publicKey);
    public String decrypt(String data, String base64PrivateKey);
}

实现类

package com.secure.encryption.decryption.rsa.service;

import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.secure.encryption.decryption.rsa.keystore.KeyStore;
@Service
public class RSAKeyPairGeneratorImpl implements RSAKeyPairGenerator{

    @Autowired
    KeyStore keystore;
    @Override
    public String KeyPairGenerator() {
        Map<String, Object> keypair = new HashMap<String, Object>();

        try {
            KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
            keyGen.initialize(1024);
            KeyPair pair = keyGen.generateKeyPair();
            final String privatestring = Base64.getEncoder().encodeToString(pair.getPrivate().getEncoded());
            final String publicstring = Base64.getEncoder().encodeToString(pair.getPublic().getEncoded());
            keystore.setPrivateKey(privatestring);
            return publicstring;
        } catch (Exception e) {
            System.err.println(e);
        }
        return null;
    }
    
    private static PublicKey getPublicKey(String base64PublicKey){
            PublicKey publicKey = null;
            try{
                X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(base64PublicKey.getBytes()));
                KeyFactory keyFactory = KeyFactory.getInstance("RSA");
                publicKey = keyFactory.generatePublic(keySpec);
                return publicKey;
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (InvalidKeySpecException e) {
                e.printStackTrace();
            }
            return publicKey;
        }

        private static PrivateKey getPrivateKey(String base64PrivateKey){
            PrivateKey privateKey = null;
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(base64PrivateKey.getBytes()));
            KeyFactory keyFactory = null;
            try {
                keyFactory = KeyFactory.getInstance("RSA");
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
            try {
                privateKey = keyFactory.generatePrivate(keySpec);
            } catch (InvalidKeySpecException e) {
                e.printStackTrace();
            }
            return privateKey;
        }


    public byte[] encrypt(String data, String publicKey) {//throws BadPaddingException, IllegalBlockSizeException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
       try {
            //AES/CBC/PKCS5Padding
            //RSA/ECB/PKCS1Padding
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, getPublicKey(publicKey));
            return cipher.doFinal(data.getBytes());
        } catch (Exception e) {
            System.err.println(e);
        }
       return null;
    }

    private static String decrypt(byte[] data, PrivateKey privateKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return new String(cipher.doFinal(data));
    }

    public String decrypt(String data, String base64PrivateKey) {//throws IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
        try {
            return decrypt(Base64.getDecoder().decode(data.getBytes()), getPrivateKey(base64PrivateKey));
        } catch (InvalidKeyException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (BadPaddingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }

}

AesUtil类

package com.secure.encryption.decryption.util;

import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.apache.tomcat.util.codec.binary.Base64;


public class AesUtil {
    private final int keySize;
    private final int iterationCount;
    private final Cipher cipher;
    
    public AesUtil(int keySize, int iterationCount) {
        this.keySize = keySize;
        this.iterationCount = iterationCount;
        try {
            cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        }
        catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw fail(e);
        }
    }
    
    public byte[] encrypt(String salt, String iv, String passphrase, String plaintext) {
        try {
            SecretKey key = generateKey(salt, passphrase);
            byte[] encrypted = doFinal(Cipher.ENCRYPT_MODE, key, iv, plaintext.getBytes("UTF-8"));
            System.out.println(encrypted);
            return encrypted;//Base64.getEncoder().encodeToString(encrypted);
            //String s = Base64.getEncoder().encodeToString(encrypted);
        }
        catch (UnsupportedEncodingException e) {
            throw fail(e);
        }
    }
    
    public String decrypt(String salt, String iv, String passphrase, String ciphertext) {
        try {
            SecretKey key = generateKey(salt, passphrase);
            byte[] decrypted = doFinal(Cipher.DECRYPT_MODE, key, iv, base64(ciphertext));
            return new String(decrypted, "UTF-8");
        }
        catch (UnsupportedEncodingException e) {
            return null;
        }catch (Exception e){
            return null;
        }
    }
    
    private byte[] doFinal(int encryptMode, SecretKey key, String iv, byte[] bytes) {
        try {
            cipher.init(encryptMode, key, new IvParameterSpec(hex(iv)));
            return cipher.doFinal(bytes);
        }
        catch (InvalidKeyException
                | InvalidAlgorithmParameterException
                | IllegalBlockSizeException
                | BadPaddingException e) {
            return null;
        }
    }
    
    private SecretKey generateKey(String salt, String passphrase) {
        try {
            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
            KeySpec spec = new PBEKeySpec(passphrase.toCharArray(), hex(salt), iterationCount, keySize);
            SecretKey key = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
            return key;
        }
        catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            return null;
        }
    }

    public static byte[] base64(String str) {
        return Base64.decodeBase64(str);
    }
    
    public static byte[] hex(String str) {
        try {
            return Hex.decodeHex(str.toCharArray());
        }
        catch (DecoderException e) {
            throw new IllegalStateException(e);
        }
    }
    
    private IllegalStateException fail(Exception e) {
        return null;
    }

}

所跟随的步骤

  1. 使用RSA密钥
  2. 生成随机AES(密钥,iv,盐)- 使用crypto.js
  3. 使用AES加密易受攻击的数据
  4. 使用RSA公钥加密AES密钥
  5. 通过网络发送加密数据和密钥
  6. 服务消耗数据解密密钥到AES
  7. 使用AES密钥解密数据
  8. 将数据发送回前端

参考HTML页面

<!DOCTYPE html>
<html>
<body>
    <script src="jquery.min.js"></script>
        <script src="jsencrypt.min.js"></script>
<script type="text/javascript" src="crypto-js.min.js"></script>
<script type="text/javascript" src="aes.js"></script>

<script type="text/javascript">


const payloadsample = {
        
        "addressLine1": "301,Kamala Mills Compound",
        "addressLine2": "Gr Flr, Tulsi Pipe Rd, Lower Parel ",
        "addressLine4": "Mumbai, Maharashtra",
        "zipcode": 400071
};


/**
Step 1 ) - get data
**/
/**
Step 2 ) - get RSA pub Key
**/

function hybridEncryption()
{
    
    
    $.ajax({
        type: 'GET',//post
        url: 'http://localhost:1818/get/rsa/public', 
        success: function(res) {
        
                let RSAEncrypt = new JSEncrypt();
                RSAEncrypt.setPublicKey(res.public);//set RSA public key
            
                const key = Math.random().toString(36).slice(2);//Generate random AES key
                console.log("key ", key);
                var iv = CryptoJS.lib.WordArray.random(128/8).toString(CryptoJS.enc.Hex);
                var salt = CryptoJS.lib.WordArray.random(128/8).toString(CryptoJS.enc.Hex);
                var aesUtil = new AesUtil(128, 1000);
                debugger
                console.log(key)
                var data = JSON.stringify({ payloadsample });
                var ciphertext = aesUtil.encrypt(salt, iv, key, data);
/**
Step 3 ) - generate key 
**/
                senData(RSAEncrypt, iv, salt, key, btoa(ciphertext))
            }, 
        error:function(e) {
            console.error(e);
        },
        contentType: "application/json",
        dataType: 'json'
    });

    
}


function senData(RSAEncrypt, iv, salt, key, base64Content)
{
        const payload = {
            "phrase":RSAEncrypt.encrypt(key),//encrypt with RSA
            "data":base64Content,
            "iv":iv,
            "salt":salt
        }
        console.log("sending : ", payload);

        $.ajax({
            type: 'POST',
            url: 'http://localhost:1818/decrypt/AES_encyptedKEY/with/RSA_privateKEY/decryptdata/with/aes',
            data: JSON.stringify (payload), // or JSON.stringify ({name: 'jonas'}),
            success: function(data) {
                 console.log(data); 
                }, 
            error:function(e) {
                console.error(e);
            },
            contentType: "application/json",
            dataType: 'json'
        });
}   

hybridEncryption();


/**
 * Incase of Backend encryption to Front end Decryption
 * decryptBE() - will get AES encrypted data with associated data
 * */

function decryptBE()
{
  
    $.ajax({
        type: 'GET',//post
        url: 'http://localhost:1818/decryptfrom/backend/aes/plain/decrypt/frontend', 
        success: function(res) {
        debugger
        var aesUtil = new AesUtil(128, 1000);

        var ciphertext = aesUtil.decrypt(res.salt, res.iv, res.key, res.data);
      console.log(ciphertext);

            }, 
        error:function(e) {
            console.error(e);
        },
        contentType: "application/json",
        dataType: 'json'
    });
}

</script>

</body>
</html> 

这是一个使用reference的工作示例。

其他使用的库是aes.js。

var AesUtil = function(keySize, iterationCount) {
  this.keySize = keySize / 32;
  this.iterationCount = iterationCount;
};

AesUtil.prototype.generateKey = function(salt, passPhrase) {
  var key = CryptoJS.PBKDF2(
      passPhrase, 
      CryptoJS.enc.Hex.parse(salt),
      { keySize: this.keySize, iterations: this.iterationCount });
  return key;
}

AesUtil.prototype.encrypt = function(salt, iv, passPhrase, plainText) {
  var key = this.generateKey(salt, passPhrase);
  var encrypted = CryptoJS.AES.encrypt(
      plainText,
      key,
      { iv: CryptoJS.enc.Hex.parse(iv) });
  return encrypted.ciphertext.toString(CryptoJS.enc.Base64);
}

AesUtil.prototype.decrypt = function(salt, iv, passPhrase, cipherText) {
  var key = this.generateKey(salt, passPhrase);
  var cipherParams = CryptoJS.lib.CipherParams.create({
    ciphertext: CryptoJS.enc.Base64.parse(cipherText)
  });
  var decrypted = CryptoJS.AES.decrypt(
      cipherParams,
      key,
      { iv: CryptoJS.enc.Hex.parse(iv) });
  return decrypted.toString(CryptoJS.enc.Utf8);
}

其余的内容

JSEncrypt v2.3.1 https://npmcdn.com/jsencrypt@2.3.1/LICENSE.txt

最后是crypto-js.min.js

谢谢,希望这对你有用


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接