安全(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),可选参数 用户认证流程: #. APP => IDS(IoT Device Server),发送HTTP请求; #. HTTP Header中包括:apikey,timestamp,hash; #. IDS将请求重新导向到随机数网页,如果缺乏随机数或者随机数已被使用; #. IDS根据HTTP Header信息查询数据库,并计算Hash,匹配则返回请求资源; #. IDS将非法请求返回未授权网页; 参数保存在HTTP Header中。 利用HTTP Header方式类似于现有的Web Cookie/Session组合方法,但有所不同。目前需要评估的是应用云与设备云,用户与设备云之间方式是否一致。后者Cookie/Session可以用于在一段时间内保持登陆状态。而目前的方式要求每次访问都计算一次密钥。 对于网络安全,请查看以下文章。 * `加盐hash保存密码的正确方式-乌云网 `_ 客户端演示算法 ------------------------- 算法采用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() :doc:`index`