SSL/TLS的理解与使用

本文出自:【InTheWorld的博客】

随着网络安全技术的发展,SSL(Secure Socket Layer)逐渐成为了一种广泛应用的技术。甚至我个人认为,SSL已经成为软件从业人员必须要理解的技术。回想起来,我为了学习SSL技术还是花了不少的时间。可惜应用的少,所以很多知识点都开始淡忘了。前几天,同事们在讨论SSL的问题,我没忍住就参合了进去,大致的讲了一下自己对SSL的一些理解,也讨论了一些相关问题。这些交流也算是简单复习了一下SSL方面的知识吧!期间也发现了自己对相关知识点有些生疏了,所以就准备写点关于SSL的问题。这篇算是个人第一篇关于网络安全的blog了。image

关于历史发展的内容,我就不赘述了。还是直接上SSL的通信流程图了,上图展示了完整的SSL通信过程。下面结合上图解释一下SSL的整个流程。

  1. 发送的第一条消息为 ClientHello,其中包含了客户端所推荐的加密参数,其中包括它准备使用的加密算法。此外,还包括一个在密钥产生过程中使用的随机值。
  2. 服务器以三条消息进行响应:首先发送选择加密与压缩算法的 ServerHello,这条消息包含一个从服务器过来的随机值。接下来,服务器发送 Certificate 消息, 其中包含服务器的公用密钥——这里是 RSA 密钥。最后,服务器发送表示握手阶段不再有任何消息的 ServerHelloDone。
  3. 客户端发送一条 ClientKeyExchange 消息,其中包含了一个随机产生的用服务器的 RSA 密钥加密的密钥。 这条消息后面跟有一条指示客户端在此之后发送的所有消息都将使用刚刚商定的密码进行加密的 ChangeCipherSpec 消息。Finished 消息包含了对整个连接过程的校验,这样服务器就能够判断要使用的加密算法是否是安全商定的。
  4. 一旦服务器接收到了客户端的 Finished 消息,它就会发送自己的 Change_Cipher_Spec 和 Finished 消息,于是连接就准备好进行应用数据的传输了。
  5. 接下去的两条消息分别是由客户端和服务器发送的应用数据。通信完毕后,发送 close_notify 消息关闭SSL连接。

在第三步的 ClientKeyExchange 消息中所包含的秘钥其实叫做pre_master_secret,这个秘钥是产生最终会话秘钥的关键所在。pre_master_secret是一个48字节的数据块,其中两个字节是版本信息。这个48字节的数据块相对比较单薄,比如3DES这种秘钥,单个的体积就达到了24个字节,加上两端的摘要秘钥以及初始化向量,这个pre_master_secret的信息是很可能不够的。所以需要pre_master_secret进行扩展,通俗说来就是把它变长。下图展示了秘钥的生成过程,从pre_master_secret一步步运算得来。

image

在秘钥的导出过程中,其实是用了秘钥导出函数。这里给出一个秘钥导出的过程的例子:

image

其中加入“A”,“BB”这些字符串是为了避免相同的Hash值。到现在为止,我们明白了一个问题,整个SSL流程的核心就在于pre_master_secret。它起到了承前启后的作用,换而言之,如果pre_master_secret如果沦陷,那么整个SSL的神殿就倒塌了。所以,pre_master_secret使用了公钥加密,常用的秘钥交换算法的强度都比较高,就是为了保证秘钥交换的安全性。

RSA算是最常见的秘钥交换算法了,我还是贴一段RSA的示例型代码吧!虽然有点长,但是可以直观理解一下RSA的工作流程。

package security;
import java.lang.reflect.Method;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;

public abstract class RSACoder {
    public static final String KEY_ALGORITHM = "RSA";
    public static final String SIGNATURE_ALGORITHM = "MD5withRSA";
    public static final String PUBLIC_KEY = "RSAPublicKey";
    public static final String PRIVATE_KEY = "RSAPrivateKey";
    public static final int KEY_SIZE = 512;

    public static String encodeBase64(byte[] input) throws Exception {
        Class clazz = Class.forName("com.sun.org.apache.xerces.internal.impl.dv.util.Base64");
        Method mainMethod = clazz.getMethod("encode", byte[].class);
        mainMethod.setAccessible(true);
        Object retObj = mainMethod.invoke(null, new Object[] { input });
        return (String) retObj;
    }

    public static byte[] decodeBase64(String input) throws Exception {
        Class clazz = Class.forName("com.sun.org.apache.xerces.internal.impl.dv.util.Base64");
        Method mainMethod = clazz.getMethod("decode", String.class);
        mainMethod.setAccessible(true);
        Object retObj = mainMethod.invoke(null, input);
        return (byte[]) retObj;
    }
    public static String sign(byte[] data, String key) throws Exception {
        byte[] keyBytes = decodeBase64(key);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initSign(privateKey);
        signature.update(data);
        return encodeBase64(signature.sign());
    }

    public static boolean verify(byte[] data, String key, String sign) throws Exception {
        byte[] keyBytes = decodeBase64(key);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PublicKey publicKey = keyFactory.generatePublic(keySpec);
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initVerify(publicKey);
        signature.update(data);
        return signature.verify(decodeBase64(sign));
    }

    public static byte[] decryptByPrivateKey(byte[] data, String key) throws Exception {
        byte[] keyBytes = decodeBase64(key);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(data);    
    }

    public static byte[] encryptByPublicKey(byte[] data, String key) throws Exception {
        byte[] keyBytes = decodeBase64(key);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return cipher.doFinal(data);
    }

    public static byte[] encryptByPrivateKey(byte[] data, String key) throws Exception {
        byte[] keyBytes = decodeBase64(key);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        return cipher.doFinal(data);                
    }

    public static byte[] decryptByPublicKey(byte[] data, String key) throws Exception {
        byte[] keyBytes = decodeBase64(key);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, publicKey);
        return cipher.doFinal(data);        
    }

    public static byte[] getPrivateKey(Map<String, Object> keyMap) throws Exception {
        Key key = (Key) keyMap.get(PRIVATE_KEY);
        return key.getEncoded();
    }

    public static byte[] getPublicKey(Map<String, Object> keyMap) throws Exception {
        Key key = (Key) keyMap.get(PUBLIC_KEY);
        return key.getEncoded();
    }

    public static Map<String, Object> initKey() throws Exception {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        keyPairGen.initialize(KEY_SIZE);
        KeyPair keyPair = keyPairGen.genKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        Map<String, Object> keyMap = new HashMap<String, Object>(2);
        keyMap.put(PRIVATE_KEY, privateKey);
        keyMap.put(PUBLIC_KEY, publicKey);
        return keyMap;
    }
    public static void main(String[] args) throws Exception{
        System.out.println("\n---私钥加密--公钥解密---");
        String inputStr = "RSA加密算法";
        System.out.println("原文:\n" + inputStr);
        Map<String, Object> keyMap = initKey();
        byte[] publicKey = getPublicKey(keyMap);
        byte[] privateKey = getPrivateKey(keyMap);
        System.out.println("公钥:\n" + encodeBase64(publicKey));
        System.out.println("私钥:\n" + encodeBase64(privateKey));
        byte[] encodeData1 = encryptByPrivateKey(inputStr.getBytes(), encodeBase64(privateKey));
        byte[] decodeData1 = decryptByPublicKey(encodeData1, encodeBase64(publicKey));
        System.out.println("私钥加密后:\n" + new String(encodeData1));
        System.out.println("共钥解密后:\n" + new String(decodeData1));
        System.out.println("\n---公钥加密--私钥解密---");
        byte[] encodeData2 = encryptByPublicKey(inputStr.getBytes(), encodeBase64(publicKey));
        byte[] decodeData2 = decryptByPrivateKey(encodeData2, encodeBase64(privateKey));
        System.out.println("公钥加密后:\n" + new String(encodeData2));
        System.out.println("私钥解密后:\n" + new String(decodeData2));
        byte[] data = inputStr.getBytes();
        byte[] sign = decodeBase64(sign(data, encodeBase64(privateKey)));
        boolean status = verify(data, encodeBase64(publicKey), encodeBase64(sign));
        System.out.println("验证结果:\n" + status);
    }
}

到这里算是又水了一篇blog,不禁想说点东西。小时候听说:是金子,到哪里都会发光。听起来很鸡汤的一句话,还是不能解决所有问题。被人看不起的感觉当然不好受,但是举目远眺,那里才是我的前方。夜郎自大者才是真正的可怜可笑之人。

已有4条评论 发表评论

  1. 123 /

    顶顶大佬,资刺了

  2. lshw4320814 / 本文作者

    承蒙小马哥看得起,哈哈!

  3. KiSoo /

    强!看了你写的很多篇,受益匪浅。

    1. lshw4320814 / 本文作者

      谢谢!能对大家有所帮助是再好不过了,😄😄

发表评论