这里主要以以太坊平台来讲解。对于其它平台,基本原理是差不多的。以太坊对外暴露了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了。