Java實現SSH模式加密原理及程式碼

qing_gee的專欄發表於2016-01-07

一、SSH加密原理

SSH是先通過非對稱加密告訴服務端一個對稱加密口令,然後進行驗證使用者名稱和密碼的時候,使用雙方已經知道的加密口令進行加密和解密,見下圖:

這裡寫圖片描述

解釋:SSH中為什麼要使用非對稱加密,又使用對稱加密,到底有什麼用處?到底安全不安全?既然後來又使用了對稱加密,開始的時候為什麼還要用非對稱加密?反過來,既然用非對稱加密,為什麼又要使用對稱加密呢?

  1. 非對稱加密,是為了將客戶端產生的256位隨機的口令傳遞到服務端,那麼在傳遞的過程中,使用公鑰進行了加密,這樣,這個256位的加密口令就很難被網路上進行破解。
  2. 對稱加密,因為頻繁的使用非對稱加密是非常浪費效能的,那麼SSH就是用了256位長度的口令作為接下來傳遞使用者名稱密碼時的加密口令,其破解的難度,想必大家都知道了,每一位上都有0-9種變化。
  3. 這樣安全嗎,我覺得還是很不錯的,具體使用起來也易於讓人理解。

二、我的SSH加密原理

①、使用場景

我所開發的專案是大宗期貨交易,主要服務於交易所,這也就產生一個需求就是,我們需要控制交易所使用我們軟體的週期。也就是說我們的專案留有一個後門,用來控制專案的週期,假如交易所使用軟體的週期到了,那麼如果他不續費,而專案的程式碼部署在人家的伺服器上,此時我們就很難控制了,但是有了這個後門,到期後會自動停止軟體,這樣就不擔心交易所不給我們錢了。

②、使用方式

  1. 我們給交易的專案程式碼中包含一個後門,該後門通過webservice client傳送一個請求到web service。
  2. web service接收到請求後,回給client需要的資訊。

在以上這個過程當中,就會產生一個SSH加密的請求方式,請允許我用一個拙劣的圖表示一下。
這裡寫圖片描述

三、我的SSH具體實現

既然要用到webservice,那麼就需要建立web service服務,還有web service client。關於這方面,我暫時不想說太多,方式有很多,我在這就不誤導大家了。我是通過eclipse搞定的,可參照webservice之間通訊 

接下來,我將介紹程式碼,但是考慮到篇幅問題,一些不必要的程式碼我就不貼出來了,關鍵在於講解清楚這個原理。

①、service

ExchangeService.java

public byte[] request(String param, String resultType) {
    logger.info("請求引數:" + param);

    // 返回物件
    KeyResult keyResult = new KeyResult();

    try {
        // 先獲取公鑰
        if (resultType.equals(PUBLIC_KEY_RESULT_TYPE)) {

            Map<String, Object> keyMap = RSACoder.initKey();
            // 產生公鑰和私鑰
            privateKey = RSACoder.getPrivateKey(keyMap);

            keyResult.setKey(RSACoder.getPublicKey(keyMap));

            logger.info("公鑰字串:" + keyResult.getKey());
            logger.info("私鑰字串:" + privateKey);

        } else if (resultType.equals(ECHOSTR_RESULT_TYPE)) {

            // 設定客戶端的口令資訊
            byte[] paramByte = new BASE64Decoder().decodeBuffer(param);
            echoStr = new String(RSACoder.decryptByPrivateKey(paramByte, privateKey));

        } else {
            // 通過資料庫獲取交易所對應的許可權資訊.
            // 先將請求轉換為byte陣列,然後再進行解密,最後轉換為字串
            ExchangeInfo info = ExchangeInfo.dao.getInfoByName(new String(CryptUtil.decrypt(
                    new BASE64Decoder().decodeBuffer(param), echoStr.getBytes())));

            String result = "";

            // 獲取系統啟用許可權
            if (resultType.equals(PRIVILEGE_RESULT_TYPE)) {
                // 先判斷使用許可權

                // 在判斷使用日期
                // 當前登入用登入時獲取登入的當前日期和開始日期進行比較,然後計算還可以使用的日期
                long time = (new Date().getTime() / 1000) - string2DateInt(openday);
                // 換算成天數
                int day = (int) (time / (60 * 60 * 24));
                // 還可以使用的天數
                if (usedays - day > 0) {
                    // 可以使用
                    result = "1";
                } else {
                    // 無法使用
                    result = "0";
                }

            }

            keyResult.setResult(CryptUtil.encrypt(result.getBytes(), echoStr.getBytes()));
        }

        return JsonUtil.objectToByte(keyResult);
    } catch (Exception e) {
        logger.error("webservice出錯了!!!!");
        logger.error(e.getMessage(), e);
    }

    return null;
}

再贅述一下:

  1. 第一個判斷語句中的內容就是生成公鑰和私鑰,並且返回公鑰。
  2. 第二個判斷語句中的內容就是儲存client傳送的隨機字串,這一步非常關鍵,隨機字串首先通過公鑰進行了加密,這大大加強了加密的深度。
  3. 第三個判斷語句中的內容就是將client的許可權通過隨機字串進行加密。

②、client

ExchangeUtil.java

public static boolean canRunForExchange(String resultType) {
    int i = 1;
    boolean result = false;

    while (true) {
        try {
            // webservice呼叫類
            ExchangeServiceProxy proxy = new ExchangeServiceProxy();
            BASE64Encoder encoder = new BASE64Encoder();

            // step1.獲取service產生的公鑰
            KeyResult keyResult = JsonUtil.byteToObject(proxy.request(null, PUBLIC_KEY_RESULT_TYPE),
                    KeyResult.class);

            // step2.產生隨機字串,傳送到webserivce
            String echoStr = StrUtil.getEchoStrByLength(10);
            byte[] echoByteParam = RSACoder.encryptByPublicKey(echoStr.getBytes(), keyResult.getKey());
            proxy.request(encoder.encode(echoByteParam), ECHOSTR_RESULT_TYPE);

            // step3.加密客戶端請求資訊,然後傳送到webservice
            // 先加密為byte陣列,然後轉換成字串
            byte[] results = proxy.request(
                    encoder.encode(CryptUtil.encrypt(Constants.client_type.getBytes(), echoStr.getBytes())),
                    resultType);
            keyResult = JsonUtil.byteToObject(results, KeyResult.class);

            // step4.通過口令解密服務端返回訊息
            String response = new String(CryptUtil.decrypt(keyResult.getResult(), echoStr.getBytes()));
            if (response.equals("1")) {
                result = true;
            }
            break;
        } catch (Exception e) {
            logger.debug("第" + i + "次載入webservice失敗");
            i++;
            logger.error(e.getMessage(), e);

            if (i >= 10) {
                break;
            }
        }
    }

    return result;
}

稍作解釋:

  1. 通過迴圈主要為了防止網路斷開時服務不停的傳送請求,最多10次就夠了。
  2. 主要有四步操作,註釋中我想解釋的還可以。

③、共享加密解密公共類

CryptUtil.java

package com.honzh.socket.util;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;

public class CryptUtil {
    /** 
    * @Title: encrypt 
    * @Description: 加密
    * @param data
    * @param key
    * @return
    * @throws Exception
    */
    public static byte[] encrypt(byte[] data, byte[] key) throws Exception {
        key = get8(key);
        Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
        DESKeySpec desKeySpec = new DESKeySpec(key);
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
        SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
        IvParameterSpec iv = new IvParameterSpec(key);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
        return cipher.doFinal(data);
    }

    /** 
    * @Title: decrypt 
    * @Description: 解密
    * @param data
    * @param key
    * @return
    * @throws Exception
    */
    public static byte[] decrypt(byte[] data, byte[] key) throws Exception {
        key = get8(key);
        Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
        DESKeySpec desKeySpec = new DESKeySpec(key);
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
        SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
        IvParameterSpec iv = new IvParameterSpec(key);
        cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
        return cipher.doFinal(data);
    }

    private static byte[] get8(byte[] key) {
        byte[] key1 = new byte[8];
        for (int i = 0; i < 8; i++) {
            key1[i] = key[i];
        }
        return key1;
    }

    public static String toHexString(byte[] data) {
        String s = "";
        for (int i = 0; i < data.length; i++) {
            s += Integer.toHexString(data[i] & 0xFF)+"-";
        }
        return s;
    }

}

一般情況下,SHA和MD5兩種加密就夠我們使用了!

至於其他的輔助類我就不多介紹了,網上有很多資源,也許你的專案也有類似的實現方式。

相關文章