首页 > 技术文章 > 编码、散列与加解密

learnhow 2017-07-23 20:49 原文

起因:前天去一家公司面试被问到数据加密的相关知识,完全回答不上来,回家后特地总结了一下。

一、编码、散列与加解密

编码:使用约定的协议对数据格式化。编码的反向操作是解码,双方并不需要专用密钥来获取真实数据。

散列:使用特定算法获取对象的数字摘要。散列是一种特殊算法,他人几乎无法伪造与原始数据完全相同的散列值,从而保证数据不会被恶意修改。但散列算法没有逆向操作,接收方不能通过散列值还原数据。

加密与解密:通过双方约定的密钥对信息加密。使用同一把密钥加解密的方式称为对称加密,使用不同密钥加解密的方式称为非对称加密。加密与编码类似只是计算过程必须有密钥参与,因此密钥是信息保密的关键。

二、编码与散列基础

编码与解码

import java.io.IOException;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

/**
 * 编码/解码
 */
public class Base {
    // encrypt:byte[] to String 
    public static String encryptBASE(byte[] src) {
        String dst = new BASE64Encoder().encode(src);
        return dst;
    }

    // decrypt:String to byte[]
    public static byte[] decryptBASE(String dst) throws IOException {
        byte[] src = new BASE64Decoder().decodeBuffer(dst);
        return src;
    }
}

散列算法常见的规则有MD5和SHA两种

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * 散列
 */
public class Digest {
    enum DIGEST_TYPE {
        MD5, SHA
    }
    
    public static byte[] hash(String src, DIGEST_TYPE type) throws NoSuchAlgorithmException {
        System.out.println(type);
        MessageDigest md = MessageDigest.getInstance(type.toString()); 
        md.update(src.getBytes());
        return md.digest();
    }
}

我们是如何保存用户密码的?

String password = "qwer1234";
// 生成摘要
byte[] digest = Digest.hash(password, DIGEST_TYPE.SHA);
String coding = Base.encryptBASE(digest);
System.out.println(coding);
// 2yXy/BTNLSseevMHJB9Uj7A8MSo=

三、对称加密——DES

对称加密顾名思义就是双方持有同一把密钥,常用的对称加密算法是DES。

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;

/**
 * DES对称加密
 */
public class DES {
    public static final String ALGORITHM = "DES";

    // byte[] to SecretKey
    private static Key getSecretKey(byte[] src)
            throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException {
        // DES 密钥
        DESKeySpec keySpec = new DESKeySpec(src);
        // 密钥的工厂
        SecretKeyFactory factory = SecretKeyFactory.getInstance(ALGORITHM);
        // 根据提供的密钥规范(密钥材料)生成 SecretKey 对象
        return factory.generateSecret(keySpec);
    }

    // 获取一个能够生成SecretKey的字符串
    public static String generateKey() throws NoSuchAlgorithmException {
        SecureRandom secureRandom = new SecureRandom();
        // 此类提供(对称)密钥生成器的功能
        KeyGenerator generatory = KeyGenerator.getInstance(ALGORITHM);
        // 初始化此密钥生成器
        generatory.init(secureRandom);
        // 生成一个密钥
        SecretKey secretKey = generatory.generateKey();
        // 对密钥使用Base64编码器编码
        return Base.encryptBASE(secretKey.getEncoded());
    }

    // 加密
    public static byte[] encrypt(byte[] src, String secretKey)
            throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException, IOException,
            NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
        Key key = getSecretKey(Base.decryptBASE(secretKey));
        // 此类为加密和解密提供密码功能
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        // 用密钥初始化此 Cipher
        cipher.init(Cipher.ENCRYPT_MODE, key);
        // 按单部分操作加密或解密数据
        return cipher.doFinal(src);
    }

    // 解密
    public static byte[] decrypt(byte[] src, String secretKey)
            throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException,
            IOException, IllegalBlockSizeException, BadPaddingException {
        Key key = getSecretKey(Base.decryptBASE(secretKey));
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, key);
        return cipher.doFinal(src);
    }

    public static void main(String[] args) throws Exception {
        String inputStr = "DES";
        String key = DES.generateKey();
        System.err.println("原文:\t" + inputStr);
        System.err.println("密钥:\t" + key);
        byte[] outputData = DES.encrypt(inputStr.getBytes(), key);

        System.err.println("加密后:\t" + Base.encryptBASE(outputData));

        outputData = DES.decrypt(outputData, key);

        System.err.println("解密后:\t" + new String(outputData));
    }
}

控制台输出:

原文:    DES
密钥:    /W3mroXN1RA=
加密后:    Z1Z3eWISSKM=
解密后:    DES

四、非对称加密——RSA

非对称加密实现逻辑要比对称加密复杂的多。首先服务器端需要生成一对公钥(PublicKey)与私钥(PrivateKey)——并持有私钥,将公钥发布出去供客户端使用。当发布数据时,服务器端利用私钥与数据生成“数字签名”(signature),该签名可以理解为发布者对应发布信息的数据摘要,从而禁止第三方伪造数据内容或篡改发布主体。接着使用私钥加密(encrypt)数据。客户端接收到数据后首先利用签名信息和公钥对密文(cryptograph)进行验证(verify),再通过公钥解密(decrypt)。而客户端向服务器发送请求的时候,首先利用公钥对原文(src)加密,服务器端则通过私钥解密。综上所述,整个流程至少应该包含以下方法:

  1. signature(byte[] src, String privateKey) // 生成数字签名
  2. verify(byte[] cryptograph, String signature, String publicKey) // 验证签名
  3. encryptByPteKey(byte[] src, String privateKey) // 使用私钥加密数据
  4. decryptByPubKey(byte[] cryptograph, String publicKey) // 使用公钥解密
  5. encryptByPubKey(byte[] src, String publicKey) // 使用公钥加密数据
  6. decryptByPteKey(byte[] cryptograph, String privateKey) // 使用私钥解密

下面是具体实现

import java.io.IOException;
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.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

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

/**
 * RSA非对称加密
 */
public class RSA {
    public static final String KEY_ALGORITHM = "RSA";
    public static final String SIGNATURE_ALGORITHM = "MD5withRSA";
    private static PublicKey publicKey;
    private static PrivateKey privateKey;

    // 生成私钥和公钥
    public static void generateKeys() throws NoSuchAlgorithmException {
        // KeyPairGenerator 类用于生成公钥和私钥对
        KeyPairGenerator pairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        // 生成一个密钥对
        KeyPair keyPair = pairGenerator.generateKeyPair();
        // 返回对此密钥对的公钥组件的引用
        publicKey = keyPair.getPublic();
        // 返回对此密钥对的私钥组件的引用
        privateKey = keyPair.getPrivate();
    }

    // 获取公钥
    public static String getPublicKey() {
        return Base.encryptBASE(publicKey.getEncoded());
    }

    // 获取私钥
    public static String getPrivateKey() {
        return Base.encryptBASE(privateKey.getEncoded());
    }

    // 使用私钥对数据生成数字签名
    public static String signature(byte[] src, String privateKey) throws IOException, NoSuchAlgorithmException,
            InvalidKeySpecException, InvalidKeyException, SignatureException {
        byte[] key = Base.decryptBASE(privateKey);
        // PKCS8EncodedKeySpec类使用PKCS#8标准作为密钥规范管理的编码格式
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(key);
        KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM);
        // 根据提供的密钥规范(密钥材料)生成私钥对象
        PrivateKey pteKey = factory.generatePrivate(spec);
        // Signature 类用来为应用程序提供数字签名算法
        Signature sign = Signature.getInstance(SIGNATURE_ALGORITHM);

        sign.initSign(pteKey);
        sign.update(src);
        return Base.encryptBASE(sign.sign());
    }

    // 使用公钥与数字签名验证密文
    public static boolean verify(byte[] cryptograph, String signature, String publicKey) throws IOException,
            NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException {
        byte[] key = Base.decryptBASE(publicKey);
        // 此类表示根据 ASN.1 类型 SubjectPublicKeyInfo 进行编码的公用密钥的 ASN.1 编码
        X509EncodedKeySpec spec = new X509EncodedKeySpec(key);
        KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM);
        // 根据提供的密钥规范(密钥材料)生成公钥对象
        PublicKey pubKey = factory.generatePublic(spec);

        Signature sign = Signature.getInstance(SIGNATURE_ALGORITHM);
        sign.initVerify(pubKey);
        sign.update(cryptograph);

        return sign.verify(Base.decryptBASE(signature));
    }

    // 使用私钥加密
    public static byte[] encryptByPteKey(byte[] src, String privateKey)
            throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException,
            InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        byte[] key = Base.decryptBASE(privateKey);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(key);
        KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM);
        PrivateKey pteKey = factory.generatePrivate(spec);

        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, pteKey);

        return cipher.doFinal(src);
    }

    // 使用公钥解密
    public static byte[] decryptByPubKey(byte[] cryptograph, String publicKey)
            throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException,
            InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        byte[] key = Base.decryptBASE(publicKey);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(key);
        KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM);
        PublicKey pubKey = factory.generatePublic(spec);

        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, pubKey);

        return cipher.doFinal(cryptograph);
    }

    // 使用公钥加密
    public static byte[] encryptByPubKey(byte[] src, String publicKey)
            throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException,
            InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        byte[] key = Base.decryptBASE(publicKey);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(key);
        KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM);
        PublicKey pubKey = factory.generatePublic(spec);

        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, pubKey);

        return cipher.doFinal(src);
    }

    public static byte[] decryptByPteKey(byte[] cryptograph, String privateKey)
            throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException,
            InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        byte[] key = Base.decryptBASE(privateKey);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(key);
        KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM);
        PrivateKey pteKey = factory.generatePrivate(spec);

        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, pteKey);

        return cipher.doFinal(cryptograph);
    }

    public static void main(String[] args)
            throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException, NoSuchPaddingException,
            IllegalBlockSizeException, BadPaddingException, IOException {
        RSA.generateKeys();
        String pubKey = RSA.getPublicKey();
        String pteKey = RSA.getPrivateKey();
        System.err.println("公钥: \n\r" + pubKey);
        System.err.println("私钥: \n\r" + pteKey);
        String inputStr = "Hello";
        byte[] data = inputStr.getBytes();

        byte[] encodedData = RSA.encryptByPteKey(data, pteKey);
        byte[] decryptData = RSA.decryptByPubKey(encodedData, pubKey);

        String outputStr = new String(decryptData);
        System.err.println("客户端获取信息: \n\r" + outputStr);
        
        inputStr = "Hi";
        data = inputStr.getBytes();
        encodedData = RSA.encryptByPubKey(data, pubKey);
        decryptData = RSA.decryptByPteKey(encodedData, pteKey);
        
        outputStr = new String(decryptData);
        System.err.println("服务器端获取信息: \n\r" + outputStr);
    }
}

控制台输出:

公钥: 
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDKf90/9LHg4YGigdQk/MaLSjciwW4NX76K3c5L
NAr/TiZCYUDGWFiAgYIegK/Ymr0fqW0vA5hIULKkCTuP4FYn3DEWBxZ57OHKgy+BbVyiHcY7KWBC
OSVijFWgSgjWg9BFiUQ2b63RIwbTpYEDVDzDYyqJUfqdh4/bVVr/4+WUbwIDAQAB
私钥: MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMp
/3T/0seDhgaKB1CT8xotKNyLB bg1fvordzks0Cv9OJkJhQMZYWICBgh6Ar9iavR+pbS8DmEhQsqQJO4/gVifcMRYHFnns4cqDL4Ft XKIdxjspYEI5JWKMVaBKCNaD0EWJRDZvrdEjBtOlgQNUPMNjKolR+p2Hj9tVWv/j5ZRvAgMBAAEC gYATliiNXhqyeL10PYCKj1SY9nW8y97cNk2U2v1wMrl5llKHCycbyEHPNDekwafAmL8ASAACkyNw ozWUPjxfn0BV8Jm5b90NWxXyyS5DJrm4DhvB3d+4e5B1YfxxvkVzvnc+J/R/jZAQ7ToAPQivyXvJ uUJgEEerHoho9jVFYOi6yQJBAOs47ORW7jRL/KWt0GKNx+laTX9hGH/SqHNdYEfX8aC2PupfdPw3 77PSJcLkcIlgsUT/guk+XCaS/mtkc/IpY30CQQDcYvovScZGCo/o2lK0G1mqosWoUTXy9G4n60wi sWX16+vrEtB4xCrNTjlfkMnKMLWNv2LdN191HlJzD3sYs8NbAkEA44jORlb81yPGAfIvyJXDkqwi mRwwWb1J60ahEv4FouOH2ql5/VySh4y5sFvPrGQXNlo/pSYId9vrNbEXI2H79QJAeozDaGZSzgHz kl1NHgATdXJ8DSPTpx1K4AHU3XneI8kj8B0PNgiHcJDuEHk37Kn3WzIwrKis+Th6SqcyIUNc/wJA Bp6Ep7qOqxce1I/SZc2lKYccLarjBJravkti6B0tBMKgiLU/iOS0UlUgx/n/De9C6rej0QihoUU6 UNwKlfg96A==

客户端获取信息: Hello 服务器端获取信息: Hi

 

后记:无

 

推荐阅读