安全(Security & Authentication)

背景说明

Web API的安全性相关设计是非常重要的一个组成部分。完整的安全设计需要涉及到:

  • 传输层安全(Transport Security)
  • 密钥管理(Keychains)
  • 用户认证(Authentication)
  • 用户授权(Authorization)
  • 用户记账(Accounting)

在互联网刚刚诞生的年代,网页(HTTP),电邮(SMTP/POP3),远程登录(Telnet),文件传输(FTP)采用明文方式传输。所以网站经常被黑挂马。现在的互联网应用中,使用SSL/TLS作为传输层安全保障成为标准配置。并在这些基础之上,构建了安全应用:HTTPS/IMAP/SSH/SFTP/VPN等。

SSL/TLS的普及提高了外部攻击者的技术门槛,降低了网站设计要求。

设备云认证机制

DawnMedical IoT Device Cloud目前采用HMAC单向认证的方式,即设备云(IDS)对用户代理(浏览器及手机APP)进行认证。无论是否采用SSL/TLS,口令均不会传输,而是通过哈希签名方式进行验证。

IoT服务器的密钥表单中包括但不限于以下信息:

  • id,即密钥数据表单中主键;
  • apikey,即提供给使用者的APIKey(明文传输);
  • secret,提供给使用者APIKey的使用密钥;
  • expire,有效时间
  • resourceId,对应的资源Id号码;
  • crud,对应CRUD操作权限;
  • master,是否为主密钥;
  • extra,其他信息。

合法用户一侧则拥有以下信息:

  • apikey,由IoT通过某种方式提供给用户/开发者;
  • secret,由IoT通过某种方式提供给用户/开发者;

如果双方计算hash一致,则认为该用户持有与apikey对应的secret,以及相对应的权限。

在hash计算中,为了保证安全,引入随机数。为了确保随机数的真实性,以及一段时间内不出现重复随机数。服务器一侧会在数据库中跟踪随机数的使用情况。目前暂时以时间戳来替代随机数。

设备云认证流程

DawnMedical IoT设备云V3 API采用认证流程:

hash=crypto(apikey + apisecret + timestamp + optional salt/random)
  • crypto: SHA256
  • apikey: IDS赋予APP的APIKey,使用uuid
  • apisecret: IDS赋予APP的APISecret,同样使用uuid
  • timestamp: 时间戳,暂不使用
  • salt:随机数(apikey盐,长度为SHA256输出长度相同,即32B),可选参数

用户认证流程:

  1. APP => IDS(IoT Device Server),发送HTTP请求;
  2. HTTP Header中包括:apikey,timestamp,hash;
  3. IDS将请求重新导向到随机数网页,如果缺乏随机数或者随机数已被使用;
  4. IDS根据HTTP Header信息查询数据库,并计算Hash,匹配则返回请求资源;
  5. IDS将非法请求返回未授权网页;

参数保存在HTTP Header中。

利用HTTP Header方式类似于现有的Web Cookie/Session组合方法,但有所不同。目前需要评估的是应用云与设备云,用户与设备云之间方式是否一致。后者Cookie/Session可以用于在一段时间内保持登陆状态。而目前的方式要求每次访问都计算一次密钥。

对于网络安全,请查看以下文章。

客户端演示算法

算法采用Python urllib2进行演示。

#!/usr/bin/env python
#encoding: utf-8

import urllib
import urllib2
import time
import binascii
from Crypto.Hash import SHA256

def makeHeaders(apikey = '71f9aefc-8ad3-40d3-84f9-d3b40f19a6cf', \
    apisecret = 'a88f3c75-0f76-4d71-b7ce-3131a4e74a1e'):
    ts = str(time.time())
    feed = "%s%s%s"%(apikey,apisecret,ts)
    h = SHA256.new()
    h.update(feed)
    d = h.digest()
    b = binascii.hexlify(d).upper()

    return {'apiKey':apikey, 'apiSecret':apisecret, 'ts':ts, 'hash': b,'referrer':'www.dawnmed.net'}

def getREST():
    headers = makeHeaders()
    url = 'http://192.168.1.130:8888/json/v3/demo'
    user_agent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) Simulated by Python'
    data = None
    req = urllib2.Request(url, data, headers)
    response = urllib2.urlopen(req)
    html = response.read()
    print(html)

def getREST_wrongKey():
    headers = makeHeaders(apikey='adskfjlkf')
    url = 'http://192.168.1.130:8888/json/v3/demo'
    user_agent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) Simulated by Python'
    data = None
    req = urllib2.Request(url, data, headers)
    response = urllib2.urlopen(req)
    html = response.read()
    print(html)

def getREST_wrongSecret():
    headers = makeHeaders(apisecret='w22elkjwelqwi')
    url = 'http://192.168.1.130:8888/json/v3/demo'
    user_agent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) Simulated by Python'
    data = None
    req = urllib2.Request(url, data, headers)
    response = urllib2.urlopen(req)
    html = response.read()
    print(html)

def postREST():
    pass

def putREST():
    pass

def delREST():
    pass

def main():
    for i in range(20):
        print(i)
        getREST()
        time.sleep(0.5)
        getREST_wrongKey()
        time.sleep(0.5)
        getREST_wrongSecret()
        time.sleep(0.5)

if __name__=='__main__':
    main()

杜恩医疗设备云及API说明