1. 程式人生 > >以太坊節點增加網路安全的一些方法

以太坊節點增加網路安全的一些方法

這裡主要以以太坊平臺來講解。對於其它平臺,基本原理是差不多的。以太坊對外暴露了RPC介面,外部應用一般是通過RPC對區塊鏈發起訪問。最普遍的是採用Http的方式來發起請求。所以許多通用的增進Http安全的方式都能在這裡派上用場。

1 Http鑑權

通過安裝nginx,然後再通過nginx配置Basic HTTP Authentication的方式,通過使用者名稱和密碼組合來對Http通訊進行加密保護。具體實現方法見文章https://blog.csdn.net/liuzhijun301/article/details/81085765

2 Http頭部增加簽名欄位

基本思路外部應用在發起Http請求的時候在Http的header增加一個數字簽名。簽名欄位可以用加密或者雜湊運算來生成。以太坊客戶端對這個數字簽名進行驗證。這裡需要修改以太坊原始碼。若應用端採用java的web3j來與go-ehereum通訊。對於web3j端,在頭部增加簽名的方法如下。

Web3j

protected static OkHttpClient buildHttpClient() {
        return new OkHttpClient.Builder().addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                RequestBody body = request.body();
                Buffer buffer = new Buffer();
                body.writeTo(buffer);
                String bodyString = buffer.readUtf8();
                String str = bodyString+"abCD#123";
                String encodeStr="";
                try{
                    MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
                    messageDigest.update(str.getBytes("UTF-8"));
                    encodeStr = Utils.byte2Hex(messageDigest.digest());
                }catch (Exception e){
                    e.printStackTrace();
                }

                Request rq = request.newBuilder().addHeader("token",encodeStr).build();
                return chain.proceed(rq);
            }
        }).build();
    }

Admin ethClient = Admin.build(new HttpService(url,buildHttpClient(),false));

這裡使用了Http攔截器,在攔截器裡面讀出請求的body,將body轉成字串,然後疊加了一個字串abCD#123到末尾,後對字串用SHA-256的方式求取雜湊值。最後給Http請求添加了一個頭部欄位token。

go-ethereum

以太坊端接收到Java應用端發過來的Http請求,會在rpc/http.go的ServeHTTP方法中對這個Http進行驗證:

func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Permit dumb empty requests for remote health-checks (AWS)
	if r.Method == http.MethodGet && r.ContentLength == 0 && r.URL.RawQuery == "" {
		return
	}
	if code, err := validateRequest(r); err != nil {
		http.Error(w, err.Error(), code)
		return
	}
    .......
}

驗證函式是validateRequest。進去這個函式:

func validateRequest(r *http.Request) (int, error) {
	if r.Method == http.MethodPut || r.Method == http.MethodDelete {
		return http.StatusMethodNotAllowed, errors.New("method not allowed")
	}
	if r.ContentLength > maxRequestContentLength {
		err := fmt.Errorf("content length too large (%d>%d)", r.ContentLength, maxRequestContentLength)
		return http.StatusRequestEntityTooLarge, err
	}
	mt, _, err := mime.ParseMediaType(r.Header.Get("content-type"))
	if r.Method != http.MethodOptions && (err != nil || mt != contentType) {
		err := fmt.Errorf("invalid content type, only %s is supported", contentType)
		return http.StatusUnsupportedMediaType, err
	}
}

在這個函式末尾新增對Http簽名的驗證:

    //這是go中讀取Http請求body的正確姿勢,先讀,再關閉,然後再重寫讀取內容
    bodyBytes, _ := ioutil.ReadAll(r.Body)
	r.Body.Close()  //  must close
	r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
	
    token := r.Header.Get("token")//接收Java端發過來的token欄位
	
    //根據body計算雜湊
    str := string(bodyBytes)+"abCD#1234"
	h := sha256.New()
	h.Write([]byte(str))
	bs := h.Sum(nil)
	ret := hex.EncodeToString(bs)
    
    //比較計算出的雜湊和token欄位是否相等
	if ret != token{
		err := fmt.Errorf("http token verify failed")
		return http.StatusForbidden,err
	}

3 配置帶證書保護的Https

Http協議的傳輸過程是明文,因此使用HTTP協議傳輸隱私資訊非常不安全。HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網路協議,要比http協議安全。,它能夠對傳輸內容進行加密。HTTPS需要申請一個CA證書,阿里雲上面可以申請免費的Symantec證書。阿里雲證書需要先有一個可用的域名,該域名對映到節點的ip。申請證書後再下載到節點中,然後在nginx的配置檔案中進行配置。

nginx配置HTTPS

以ubuntu16.04為例,開啟配置檔案/etc/nginx/sites-enabled/default檔案:

server {
	listen 80 default_server;
	listen [::]:80 default_server;

    SSL configuration
	#
	listen 443 ssl default_server;
	listen [::]:443 ssl default_server;
	ssl                  on;  
    ssl_certificate      /cert/xxx.crt;
	ssl_certificate_key  /cert/xxx.key;
	ssl_session_timeout 5m;
    ssl_protocols  TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers AESGCM:ALL:!DH:!EXPORT:!RC4:+HIGH:!MEDIUM:!LOW:!aNULL:!eNULL;
    ssl_prefer_server_ciphers on;
  .......
}  

重啟一下nginx服務既可以使用https了。