安全(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可以用于在一段时间内保持登陆状态。而目前的方式要求每次访问都计算一次密钥。
对于网络安全,请查看以下文章。
客户端演示算法¶
算法采用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()