陪玩小程式原始碼,不容錯過的加密演算法整理清單

云豹科技-苏凌霄發表於2024-07-06

陪玩小程式原始碼,不容錯過的加密演算法整理清單

在開發陪玩小程式原始碼時,可採用的加密演算法型別包含:

對稱加密

對稱加密演算法,使用Cipher類即可,以廣泛使用的AES為例,如下:

public byte[] encrypt(byte[] data, Key key) {
    try {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        byte[] iv = SecureRandoms.randBytes(cipher.getBlockSize());
        //初始化金鑰與加密引數iv
        cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
        //加密
        byte[] encryptBytes = cipher.doFinal(data);
        //將iv與密文拼在一起
        ByteArrayOutputStream baos = new ByteArrayOutputStream(iv.length + encryptBytes.length);
        baos.write(iv);
        baos.write(encryptBytes);
        return baos.toByteArray();
    } catch (Exception e) {
        return ExceptionUtils.rethrow(e);
    }
}

public byte[] decrypt(byte[] data, Key key) {
    try {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        //獲取密文前面的iv
        IvParameterSpec ivSpec = new IvParameterSpec(data, 0, cipher.getBlockSize());
        cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
        //解密iv後面的密文
        return cipher.doFinal(data, cipher.getBlockSize(), data.length - cipher.getBlockSize());
    } catch (Exception e) {
                return ExceptionUtils.rethrow(e);
    }
}

如上,對稱加密主要使用Cipher,不管是AES還是DES,Cipher.getInstance()傳入不同的演算法名稱即可。
另外,為了使得每次加密出來的密文不同,我使用了隨機的iv向量,並將iv向量拼接在了密文前面。

非對稱加密

非對稱加密同樣是使用Cipher類,只是傳入的金鑰物件不同,以RSA演算法為例,如下:

public byte[] encryptByPublicKey(byte[] data, PublicKey publicKey){
    try{
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return cipher.doFinal(data);
    }catch (Exception e) {
        throw Errors.toRuntimeException(e);
    }
}

public byte[] decryptByPrivateKey(byte[] data, PrivateKey privateKey){
    try{
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }catch (Exception e) {
        throw Errors.toRuntimeException(e);
    }
}

一般來說應使用公鑰加密,私鑰解密,但其實反過來也是可以的。

密碼學雜湊

密碼學雜湊演算法包括MD5、SHA1、SHA256等,在JCA中都使用MessageDigest類即可,如下:

public static String sha256(byte[] bytes) throws NoSuchAlgorithmException {
    MessageDigest digest = MessageDigest.getInstance("SHA-256");
    digest.update(bytes);
    return Hex.encodeHexString(digest.digest());
}

訊息認證碼

訊息認證碼使用Mac類實現,以常見的HMAC搭配SHA256為例,如下:

public byte[] digest(byte[] data, Key key) throws InvalidKeyException, NoSuchAlgorithmException{
    Mac mac = Mac.getInstance("HmacSHA256");
    mac.init(key);
    return mac.doFinal(data);
}

數字簽名

數字簽名使用Signature類實現,以RSA搭配SHA256為例,如下:

public byte[] sign(byte[] data, PrivateKey privateKey) {
    try {
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(data);
        return signature.sign();
    } catch (Exception e) {
        return ExceptionUtils.rethrow(e);
    }
}

public boolean verify(byte[] data, PublicKey publicKey, byte[] sign) {
    try {
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initVerify(publicKey);
        signature.update(data);
        return signature.verify(sign);
    } catch (Exception e) {
        return ExceptionUtils.rethrow(e);
    }
}

金鑰協商演算法

在JCA中,使用KeyAgreement來呼叫金鑰協商演算法,以ECDH協商演算法為例,如下:

public static void testEcdh() {
    KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
    ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
    keyGen.initialize(ecSpec);
    // A生成自己的私密資訊
    KeyPair keyPairA = keyGen.generateKeyPair();
    KeyAgreement kaA = KeyAgreement.getInstance("ECDH");
    kaA.init(keyPairA.getPrivate());
    // B生成自己的私密資訊
    KeyPair keyPairB = keyGen.generateKeyPair();
    KeyAgreement kaB = KeyAgreement.getInstance("ECDH");
    kaB.init(keyPairB.getPrivate());

    // B收到A傳送過來的公用資訊,計算出對稱金鑰
    kaB.doPhase(keyPairA.getPublic(), true);
    byte[] kBA = kaB.generateSecret();

    // A收到B傳送過來的公開資訊,計算對對稱金鑰
    kaA.doPhase(keyPairB.getPublic(), true);
    byte[] kAB = kaA.generateSecret();
    Assert.isTrue(Arrays.equals(kBA, kAB), "協商的對稱金鑰不一致");
}

基於口令加密PBE

通常,對稱加密演算法需要使用128位位元組的金鑰,但這麼長的金鑰使用者是記不住的,使用者容易記住的是口令,也即password,但與金鑰相比,口令有如下弱點:

1、口令通常較短,這使得直接使用口令加密的強度較差。
2、口令隨機性較差,因為使用者一般使用較容易記住的東西來生成口令。

為了使得使用者能直接使用口令加密,又能最大程度避免口令的弱點,於是PBE(Password Based Encryption)演算法誕生,思路如下:

既然密碼演算法需要金鑰,那在加解密前,先使用口令生成金鑰,然後再使用此金鑰去加解密。
為了彌補口令隨機性較差的問題,生成金鑰時使用隨機鹽來混淆口令來產生準金鑰,再使用雜湊函式對準金鑰進行多次雜湊迭代,以生成最終的金鑰。
因此,使用PBE演算法進行加解密時,除了要提供口令外,還需要提供隨機鹽(salt)與迭代次數(iteratorCount),如下:

public static byte[] encrypt(byte[] plainBytes, String password, byte[] salt, int iteratorCount) {
    try {
        PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
        SecretKey key = SecretKeyFactory.getInstance("PBEWithMD5AndTripleDES").generateSecret(keySpec);

        Cipher cipher = Cipher.getInstance("PBEWithMD5AndTripleDES");
        cipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(salt, iteratorCount));
        byte[] encryptBytes = cipher.doFinal(plainBytes);
        byte[] iv = cipher.getIV();
        ByteArrayOutputStream baos = new ByteArrayOutputStream(iv.length + encryptBytes.length);
        baos.write(iv);
        baos.write(encryptBytes);
        return baos.toByteArray();
    } catch (Exception e) {
        throw Errors.toRuntimeException(e);
    }
}

public static byte[] decrypt(byte[] secretBytes, String password, byte[] salt, int iteratorCount) {
    try {
        PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
        SecretKey key = SecretKeyFactory.getInstance("PBEWithMD5AndTripleDES").generateSecret(keySpec);

        Cipher cipher = Cipher.getInstance("PBEWithMD5AndTripleDES");
        IvParameterSpec ivParameterSpec = new IvParameterSpec(secretBytes, 0, cipher.getBlockSize());
        cipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(salt, iteratorCount, ivParameterSpec));
        return cipher.doFinal(secretBytes, cipher.getBlockSize(), secretBytes.length - cipher.getBlockSize());
    } catch (Exception e) {
        throw Errors.toRuntimeException(e);
    }
}

public static void main(String[] args) throws Exception {
    byte[] content = "hello".getBytes(StandardCharsets.UTF_8);
    byte[] salt = Base64.decode("QBadPOP6/JM=");
    String password = "password";
    byte[] encoded = encrypt(content, password, salt, 1000);
    System.out.println("密文:" + Base64.encode(encoded));
    byte[] plainBytes = decrypt(encoded, password, salt, 1000);
    System.out.println("明文:" + new String(plainBytes, StandardCharsets.UTF_8));
}

注意,雖然使用PBE加解密資料,都需要使用相同的password、salt、iteratorCount,但這裡面只有password是需要保密的,salt與iteratorCount不需要,可以儲存在資料庫中,比如每個使用者註冊時給他生成一個隨機鹽。

到此,JCA密碼演算法就介紹完了,來回顧一下:

整體來說,JCA對密碼演算法相關的類設計與封裝還是非常清晰簡單的!

以上就是陪玩小程式原始碼,不容錯過的加密演算法整理清單, 更多內容歡迎關注之後的文章

相關文章