1. 程式人生 > >安全之加密演算法(-)

安全之加密演算法(-)

**目前各種系統,特別是政府和金融領域的系統對於系統的安全都是特別重視的,提到安全,不得不提加密演算法,提到加密演算法不得不提的兩種加密型別:
對稱加密和非對稱加密**
抄下百度吧:

對稱加密:

需要對加密和解密使用相同金鑰的加密演算法。由於其速度快,對稱性加密通常在訊息傳送方需要加密大量資料時使用。對稱性加密也稱為金鑰加密。
所謂對稱,就是採用這種加密方法的雙方使用方式用同樣的金鑰進行加密和解密。金鑰是控制加密及解密過程的指令。演算法是一組規則,規定如何進行加密和解密。
因此[1] 加密的安全性不僅取決於加密演算法本身,金鑰管理的安全性更是重要。因為加密和解密都使用同一個金鑰,如何把金鑰安全地傳遞到解密者手上就成了必須要解決的問題。

非對稱加密:

1976年,美國學者Dime和Henman為解決資訊公開傳送和金鑰管理問題,提出一種新的金鑰交換協議,允許在不安全的媒體上的通訊雙方交換資訊,安全地達成一致的金鑰,這就是“公開金鑰系統”。
與對稱加密演算法不同,非對稱加密演算法需要兩個金鑰:公開金鑰(publickey)和私有金鑰(privatekey)。公開金鑰與私有金鑰是一對,如果用公開金鑰對資料進行加密,只有用對應的私有金鑰才能解密;如果用私有金鑰對資料進行加密,那麼只有用對應的公開金鑰才能解密。因為加密和解密使用的是兩個不同的金鑰,所以這種演算法叫作非對稱加密演算法。

數字簽名:

數字簽名(又稱公鑰數字簽名、電子簽章)是一種類似寫在紙上的普通的物理簽名,但是使用了公鑰加密領域的技術實現,用於鑑別數字資訊的方法。一套數字簽名通常定義兩種互補的運算,一個用於簽名,另一個用於驗證。
數字簽名,就是隻有資訊的傳送者才能產生的別人無法偽造的一段數字串,這段數字串同時也是對資訊的傳送者傳送資訊真實性的一個有效證明。
數字簽名是非對稱金鑰加密技術與數字摘要技術的應用。

總結:
對稱加密是指加密和解密採用相同或者相似的祕鑰,常用的有:des,aes,三重des

非對稱加密是指加密和解密採用不同的祕鑰,加密的叫做公鑰,解密的叫做私鑰,公鑰是可以公開的,其實這麼說也不完全對,非對稱加密是成對出現的,任何一方都可以用作加密也都可以公開,公開的一方即是公鑰,不公開的一方即是私鑰.
不過一般公鑰加密,私鑰解密用作加密
私鑰加密,公鑰解密用作簽名

從公鑰無法推算出私鑰,同理反過來也是如此.
常用有rsa(其實我就知道這一個)

優缺點:
對稱加密較快,但是相對安全性較弱,,一般適用於資料量較大.祕鑰的儲存是個問題,如果完全依賴於使用者本身,是不可靠的,任何系統考慮系統安全性時,都應將使用者的可靠性置於極低的位置.

非對稱加密,演算法複雜,速度較慢,安全性較高,適用於較少資料量.

所以一般系統資料量較大時,一般採用:對稱加密資料資訊,非對稱加密對稱祕鑰.

java中應用介紹:

對於這些公開的加密演算法,java底層都有用c或c++實現,jni呼叫即可,所以我們在java中使用這些演算法時,不需要繁瑣的操作

java中aes對稱加密實現

package com.taoyuan.demo;


import java.security.SecureRandom;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;


import com.taoyuan.demo.ByteUtil.StringType;
public class AesUtil {
    public static final String DEFAULT_AES_TYPE="AES/CBC/PKCS5Padding";
    public static final byte[] DEFAULT_AES_IV="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0".getBytes();//指定預設偏移量
    public static final StringType STRING_TYPE=StringType.BASE64;
    /**加密二進位制,這種初始化對稱祕鑰方式,可以實現php和java的互通,根據password的位數16位 128 32位 256區別加密位數
     * @param data
     * @param password
     * @return
     * @throws Exception
     */
    public static byte[] encrypt(byte[] data,String password) throws Exception{
        Cipher cipher = Cipher.getInstance(DEFAULT_AES_TYPE);// 建立密碼器(採用padding方式,解決明文長度不為16的倍數問題)
        cipher.init(Cipher.ENCRYPT_MODE, getKey1(password),new IvParameterSpec(DEFAULT_AES_IV) );// 初始化
        byte[] result = cipher.doFinal(data);
        return result;// 加密 
    }

    /**解密二進位制
     * @param data
     * @param password
     * @retu
     * @throws Exception
     */
    public static byte[] decrypt(byte[] data,String password) throws Exception{
        Cipher cipher = Cipher.getInstance(DEFAULT_AES_TYPE);// 建立密碼器(採用padding方式,解決明文長度不為16的倍數問題)
        cipher.init(Cipher.DECRYPT_MODE, getKey1(password), new IvParameterSpec(DEFAULT_AES_IV) );// 初始化
        byte[] result = cipher.doFinal(data);
        return result;
    }
/*  預設加密模式:AES/CBC/PKCS5Padding(此種方式的加密資料無法滿足跨平臺的需求,應為祕鑰的生成是根據密碼加隨機數,
             *其他平臺無法或者很難重現這種過程)*/
    /**
     * 加密
     * 
     * @param content
     *            需要加密二進位制內容
     * @param password
     *            加密密碼
     * @return
     */
    public static  byte[] encryptJava(byte[] content,String password) {
        try {
            Cipher cipher = Cipher.getInstance("AES");// 建立密碼器
            cipher.init(Cipher.ENCRYPT_MODE, getKey(password));// 初始化
            byte[] result = cipher.doFinal(content);
            return result; // 加密
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 解密
     * 
     * @param content
     *            解密二進位制
     * @param password
     *            解密金鑰
     * @return
     */
    public static byte[] decryptJava(byte[] content,String password) {
        try {
            Cipher cipher = Cipher.getInstance("AES");// 建立密碼器
            cipher.init(Cipher.DECRYPT_MODE, getKey(password));// 初始化
            byte[] result = cipher.doFinal(content);
            return result; // 加密
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    //加解密字串

    public static String encrypt(String data,String password) throws Exception{
        return ByteUtil.byteToString(encrypt(data.getBytes(), password),STRING_TYPE);
    }
    public static String decrypt(String data,String password) throws Exception{
        return new String(decrypt(ByteUtil.stringToByte(data, STRING_TYPE), password));
    }

    public static String encryptJava(String data,String password) throws Exception{
        return ByteUtil.byteToString(encryptJava(data.getBytes(), password),STRING_TYPE);
    }
    public static String decryptJava(String data,String password) throws Exception{
        return new String(decryptJava(ByteUtil.stringToByte(data, STRING_TYPE), password));
    }


    //這種初始化密碼方式,不跨平臺,只在java語言使用,安全性較高
    public static SecretKey getKey(String password) throws Exception{
        KeyGenerator kgen = KeyGenerator.getInstance("AES");
        //解決linux異常
        SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG" );  
        secureRandom.setSeed(password.getBytes()); 
        kgen.init(256, secureRandom);
        SecretKey secretKey = kgen.generateKey();
        byte[] enCodeFormat = secretKey.getEncoded();
        SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
        return key;
    }
    //直接使用密碼作為對稱祕鑰,跨平臺
    public static SecretKey getKey1(String password){
        return new SecretKeySpec(password.getBytes(), "AES");//建立aes格式的密碼
    }

}

php中aes加密實現,可以與上面的aes加密實現互通

<?php

namespace common\lib\aes;
/**
 * @method encrypt AES加密演算法 其中padding為pkcs5padding演算法
 * @method decrypt AES解密演算法
 * @method substr 字串擷取
 */
class AES
{

    /**
     * @TODO AES加密
     * @param $string the string to encrypt
     * @param $key the key for encrypt 128位加密 傳16為key 256位加密傳32位key
     * @param $iv the iv for encrypt 128位加密 傳16為iv 256位加密傳32位iv
     * @param $algorithm AES length mode(MCRYPT_RIJNDAEL_128,MCRYPT_RIJNDAEL_192,MCRYPT_RIJNDAEL_256)
     * @param $mode AES encrypt (such as MCRYPT_MODE_CBC,MCRYPT_MODE_ECB..)
     * @param $padding need or not to padding
     * @return string
     */
    static public function encrypt($string, $key=null, $iv = null , $algorithm = MCRYPT_RIJNDAEL_128, $mode = MCRYPT_MODE_CBC, $padding = true, $base64 = true)
    {
        if(!is_string($string) or empty($string)) return '';
        if(!extension_loaded('mcrypt')) throw new \Exception('AesEncrypt requires PHP mcrypt extension to be loaded in order to use data encryption feature.');

        //mcrypt init
        $module = mcrypt_module_open($algorithm, '', $mode, '');

        if(is_null($iv)){
            //偏移量為0
            $iv = str_repeat("\0", 16);
        }

        mcrypt_generic_init($module, $key, $iv);

        if($padding){
            $block = mcrypt_get_block_size($algorithm, $mode);
            $pad = $block - (strlen($string) % $block); //Compute how many characters need to pad
            $string .= str_repeat(chr($pad), $pad);  // After pad, the str length must be equal to block or its integer multiples
        }

        //encrypt
        $encrypted = mcrypt_generic($module, $string);

        //Close
        mcrypt_generic_deinit($module);
        mcrypt_module_close($module);
        return $base64 ? base64_encode($encrypted) : $encrypted;
    }

    /**
     * @TODO AES解密
     * @param $string the string to decrypt
     * @param $key the key for decrypt 128位加密 傳16為key 256位加密傳32位key
     * @param $iv the iv for decrypt 128位加密 傳16為iv 256位加密傳32位iv
     * @param $algorithm AES length mode(MCRYPT_RIJNDAEL_128,MCRYPT_RIJNDAEL_192,MCRYPT_RIJNDAEL_256)
     * @param $mode AES decrypt (such as MCRYPT_MODE_CBC,MCRYPT_MODE_ECB..)
     * @param $padding need or not to padding
     * @return string
     */
    static public function decrypt($encryptString, $key = null, $iv = null, $algorithm = MCRYPT_RIJNDAEL_128, $mode = MCRYPT_MODE_CBC)
    {

        if(!is_string($encryptString) or empty($encryptString)) return '';

        $encryptString = base64_decode($encryptString);

        if(!extension_loaded('mcrypt')) throw new \Exception('AesEncrypt requires PHP mcrypt extension to be loaded in order to use data encryption feature.');

        $module = mcrypt_module_open($algorithm, '', $mode, '');

        if(is_null($iv))
        {
            $iv = str_repeat("\0",16);
        }
        mcrypt_generic_init($module, $key, $iv);

        /* Decrypt encrypted string */
        $decrypted = mdecrypt_generic($module, $encryptString);

        /* Terminate decryption handle and close module */
        mcrypt_generic_deinit($module);
        mcrypt_module_close($module);

        return rtrim($decrypted, "\0");
    }
}

java中rsa非對稱加密,簽名驗籤實現

package com.taoyuan.demo;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Enumeration;
import java.util.Properties;
import javax.crypto.Cipher;
import com.taoyuan.demo.support.SupportUtil;

public class RSAUtil {
      public static final String CERT_TYPE_P12 = "PKCS12";

        public static final String ENCRYPT_TYPE_RSA = "RSA";
        public static final String DEFAILT_ALGORITHM = "MD5withRSA";//MD5withRSA,SHA1WithRSA,SHA256WithRSA,SHA384WithRSA
        public static final String CERT_TYPE_X509 = "X.509";
        public static final String DEFAULT_CER = "server.cer";//預設通訊方公鑰
        public static final String DEFAULT_CLIENT_P12 = "client.p12";//客戶端證書
        public static KeyStore keyStore;
        public static String keyPassword;
        static{
            try {
                Properties pro=new Properties();
                InputStream resourceAsStream = RSAUtil.class.getClassLoader().getResourceAsStream("config.properties");
                pro.load(resourceAsStream);
                keyStore = KeyStore.getInstance(CERT_TYPE_P12);
                InputStream input=null;
                String certPath=pro.getProperty("p12Path");
                File certFile=new File(certPath);
                if(SupportUtil.isEmpty(certPath)||!certFile.exists()){
                    input=RSAUtil.class.getClassLoader().getResourceAsStream(DEFAULT_CLIENT_P12);
                }else{
                    input=new FileInputStream(certPath);
                }
                keyPassword=pro.getProperty("certPWD");
                keyStore.load(input, keyPassword.toCharArray());
            } catch (Exception e) {
                e.printStackTrace();
              System.err.print("載入p12證書失敗\n"+e);
            }
        }
         private static RSAPublicKey getPublicKey() throws Exception {
            String key_aliases = null;
            Enumeration<String> enumeration = keyStore.aliases();
            key_aliases = enumeration.nextElement();
            if (keyStore.isKeyEntry(key_aliases)) {
                RSAPublicKey publicKey = (RSAPublicKey) keyStore.getCertificate(key_aliases).getPublicKey();
                return publicKey;
            }
            return null;
        }
        private static RSAPrivateKey getPrivateKey() throws Exception {
            String key_aliases = null;
            Enumeration<String> enumeration = keyStore.aliases();
            key_aliases = enumeration.nextElement();
            if (keyStore.isKeyEntry(key_aliases)) {
                RSAPrivateKey privateKey = (RSAPrivateKey) keyStore.getKey(key_aliases, keyPassword.toCharArray());
                return privateKey;
            }
            return null;
        }
        /**
         * 公鑰加密
         * 
         * @param data
         * @param publicKey
         * @return
         * @throws Exception
         */
        public static byte[] encryptByPublicKey(byte[] data)
                throws Exception {
         RSAPublicKey publicKey=getPublicKey();
            Cipher cipher = Cipher.getInstance(ENCRYPT_TYPE_RSA);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            // 模長
            int key_len = publicKey.getModulus().bitLength() / 8;
            // 加密資料長度 <= 模長-11,如果明文長度大於模長-11則要分組加密
            key_len-=11;
            //分組加密
            ByteArrayOutputStream out=new ByteArrayOutputStream();
            for (int i = 0; i < data.length; i += key_len) {
                byte[] doFinal = cipher.doFinal(SupportUtil.subarray(data, i, i + key_len));
                out.write(doFinal);
            }
            return out.toByteArray();
        }




        /**
         * 私鑰解密
         * 
         * @param data
         * @param privateKey
         * @return
         * @throws Exception
         */
        public static byte[] decryptByPrivateKey(byte[] data)
                throws Exception {
             RSAPrivateKey privateKey=getPrivateKey();
            Cipher cipher = Cipher.getInstance(ENCRYPT_TYPE_RSA);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            //模長
            int key_len = privateKey.getModulus().bitLength() / 8;
            //分組解密
            ByteArrayOutputStream out=new ByteArrayOutputStream();
            //如果密文長度大於模長則要分組解密
              for (int i = 0; i < data.length; i+=key_len) {
                    byte[] doFinal = cipher.doFinal(SupportUtil.subarray(data, i, i+key_len));
                    out.write(doFinal);
           }

              return out.toByteArray();
    }
            //驗證簽名資料
            public static boolean vefySign(byte[] content,byte[] signvalue,PublicKey  publicKey){
                try {
                    if(publicKey==null)publicKey=getPublicKey();
                    Signature signature = Signature.getInstance(DEFAILT_ALGORITHM);
                    signature.initVerify(publicKey);
                    signature.update(content);
                    boolean bverify = signature.verify(signvalue);
                    return bverify;
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return false;

            }
          //簽名資料
            public static byte[] signData(byte[] data) throws Exception{
                PrivateKey privateKey = getPrivateKey();
                Signature signture = Signature.getInstance(DEFAILT_ALGORITHM);
                signture.initSign(privateKey);
                signture.update(data);
                return signture.sign();
            }
            //使用外部的key加密
            public static byte[] encryptByPublicKey(byte[] data,String publicKeyPath)
                    throws Exception {
                RSAPublicKey rsaPublicKey=(RSAPublicKey)getPublicKeyCer(publicKeyPath);
                Cipher cipher = Cipher.getInstance(ENCRYPT_TYPE_RSA);
                cipher.init(Cipher.ENCRYPT_MODE, rsaPublicKey);
                // 模長
                int key_len = rsaPublicKey.getModulus().bitLength() / 8;
                // 加密資料長度 <= 模長-11,如果明文長度大於模長-11則要分組加密
                key_len-=11;
                //分組加密
                ByteArrayOutputStream out=new ByteArrayOutputStream();
                for (int i = 0; i < data.length; i += key_len) {
                    byte[] doFinal = cipher.doFinal(SupportUtil.subarray(data, i, i + key_len));
                    out.write(doFinal);
                }
                return out.toByteArray();
            }
            //從cer證書讀取公鑰
            public static PublicKey getPublicKeyCer(String cerPath) throws Exception {
                CertificateFactory certificatefactory=CertificateFactory.getInstance(CERT_TYPE_X509);
                InputStream bais=null;
                 if(SupportUtil.isEmpty(cerPath)){
                     bais = RSAUtil.class.getClassLoader().getResourceAsStream(DEFAULT_CER);
                 }else{
                     bais=new FileInputStream(cerPath);
                 }
                 X509Certificate Cert = (X509Certificate)certificatefactory.generateCertificate(bais);
                 PublicKey publicKey = Cert.getPublicKey();
                return publicKey;
            }



}

輔助工具類和包
使用了 commons-codec-1.10
位元組陣列轉字串,便於網路傳輸,目前支援三種,base64,hex,ascii

//型別列舉
package com.taoyuan.demo;

/**
 * @author 桃源 位元組陣列轉字元
 *2017年10月6日 下午8:21:24
 */
public enum StringType {
        BASE64,HEX,ASCII;
}

//轉換實現

package com.taoyuan.demo;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.BinaryCodec;
import org.apache.commons.codec.binary.Hex;


public class ByteUtil {

    public static String byteToString(byte[] data,StringType type) throws Exception{
        switch (type) {
        case BASE64:
            return Base64.encodeBase64String(data);
        case HEX:
            return Hex.encodeHexString(data);
        case ASCII:
            return BinaryCodec.toAsciiString(data);
        default:
            return Base64.encodeBase64String(data);
        }
    }
    public static byte[] stringToByte(String data,StringType type) throws Exception{
        switch (type) {
        case BASE64:
            return Base64.decodeBase64(data);
        case HEX:
            return Hex.decodeHex(data.toCharArray());
        case ASCII:
            return BinaryCodec.fromAscii(data.getBytes());
        default:
            return Base64.decodeBase64(data);
        }
    }
    public static void main(String[] args) throws Exception {
        String byteToString = byteToString("123".getBytes(),StringType.HEX );
        System.out.println(new String(stringToByte(byteToString, StringType.HEX )));
    }
}

使用測試

package com.taoyuan.demo.test;

import com.taoyuan.demo.AesUtil;
import com.taoyuan.demo.ByteUtil;
import com.taoyuan.demo.RSAUtil;
import com.taoyuan.demo.StringType;

public class Test {
    public static void main(String[] args) throws Exception {
        String data="123";
        String password="1234567812345678";
        //對稱加密解密
        String en=AesUtil.encrypt(data, password);
        System.out.println(en);
        System.out.println(AesUtil.decrypt(en,password));

        String en2=AesUtil.encryptJava(data, password);
        System.out.println(en2);
        System.out.println(AesUtil.decryptJava(en2,password));


        //非對稱加密解密
        String en3 = ByteUtil.byteToString(RSAUtil.encryptByPublicKey(data.getBytes()),StringType.BASE64);
        System.out.println(en3);
        System.out.println(new String(RSAUtil.decryptByPrivateKey(ByteUtil.stringToByte(en3, StringType.BASE64))));
        //簽名和驗籤
        String signValue=ByteUtil.byteToString(RSAUtil.signData(data.getBytes()), StringType.BASE64);
        System.out.println(signValue);
        System.out.println(RSAUtil.vefySign("123".getBytes(), ByteUtil.stringToByte(signValue, StringType.BASE64), null));
    }

}