ncryptf Swift

License

操作系统 / Swift 构建状态
Linux 5.0
MacOS 5.0

ncryptf logo

一个库,用于促进基于哈希的 KDF 签名认证,以及与兼容 API 进行端到端加密通信。

安装

可以通过 Swift Package Manager 安装此库,方法是添加以下依赖项

dependencies: [
    .package(url: "https://github.com/ncryptf/ncryptf-swift.git", , .upToNextMinor(from: "0.2.0")),
],

测试

MacOS

MacOS 测试通过 swift test 命令运行

swift test

Linux

Linux 测试可以在本地运行,也可以通过提供的 docker 容器运行

本地测试

apt install libsodium-dev
swift test

Docker 测试

如果您正在使用的平台不支持 swift 或 XCTestCase,您可以通过 Docker 运行测试套件

首先,构建 docker 镜像

docker build --tag ncryptf --compress --squash .

然后可以按如下方式运行测试

docker run -it -v${PWD-.}:/package ncryptf swift test

HMAC+HKDF 身份验证

HMAC+HKDF 身份验证是一种身份验证方法,可确保请求在传输过程中未被篡改。这不仅可以防止网络层操纵,还可以防止中间人攻击。

在高层面上,HMAC 签名是基于原始请求正文、HTTP 方法、URI(如果存在查询参数)和当前日期创建的。除了确保请求在传输过程中不会被操纵外,它还确保请求是限时的,有效防止重放攻击。

该库本身可以通过导入以下结构体来使用

支持的 API 将返回以下有效负载,其中至少包含以下信息。

{
    "access_token": "7XF56VIP7ZQQOLGHM6MRIK56S2QS363ULNB5UKNFMJRQVYHQH7IA",
    "refresh_token": "7XF56VIP7ZQQOLGHM6MRIK56S2QS363ULNB5UKNFMJRQVYHQH7IA",
    "ikm": "bDEyECRvKKE8w81fX4hz/52cvHsFPMGeJ+a9fGaVvWM=",
    "signing": "7v/CdiGoEI7bcj7R2EyDPH5nrCd2+7rHYNACB+Kf2FMx405und2KenGjNpCBPv0jOiptfHJHiY3lldAQTGCdqw==",
    "expires_at": 1472678411
}

提取元素后,我们可以通过执行以下操作来创建签名请求

let auth = try? Authorization(
    httpMethod: httpMethod,
    uri: uri,
    token: token,
    date: Date(),
    payload: payload
)

if auth = auth {
    let header = auth.getHeader()!
}

一个简单的完整示例如下所示

let token = Token(
    accessToken: "7XF56VIP7ZQQOLGHM6MRIK56S2QS363ULNB5UKNFMJRQVYHQH7IA",
    refreshToken: "7XF56VIP7ZQQOLGHM6MRIK56S2QS363ULNB5UKNFMJRQVYHQH7IA",
    ikm: Data(base64Encoded: "bDEyECRvKKE8w81fX4hz/52cvHsFPMGeJ+a9fGaVvWM=")!,
    signature: Data(base64Encoded: "7v/CdiGoEI7bcj7R2EyDPH5nrCd2+7rHYNACB+Kf2FMx405und2KenGjNpCBPv0jOiptfHJHiY3lldAQTGCdqw==")!,
    expiresAt: Date(timeIntervalSinceReferenceDate: 1472678411)
)

let date = Date()

let auth = try? Authorization(
    httpMethod: "POST",
    uri: "/api/v1/test",
    token: token,
    date: date,
    payload: "{\"foo\":\"bar\"}".data(using: .utf8, allowLossyConversion: false)
)

if auth = auth {
    let header = auth.getHeader()!
}

请注意,调用 Authorization 时,date 属性应为预偏移,以防止时间偏差。

Authorization:init 中的 payload 参数应为 JSON 可序列化字符串。

版本 2 HMAC 标头

对于支持版本 2 HMAC 标头的 API,可以通过调用以下方法检索

if auth = auth {
    let header = auth.getHeader()!
}

版本 1 HMAC 标头

对于使用版本 1 HMAC 标头的 API,请调用 Authorization 并将可选的 version 参数设置为 1 作为第 6 个参数。

let auth = try? Authorization(
    httpMethod: httpMethod,
    uri: uri,
    token: token,
    date: Date(),
    payload: payload,
    version: 1
)

if auth = auth {
    let header = auth.getHeader()!
}

此字符串可用于 Authorization 标头

日期标头

版本 1 HMAC 标头需要额外的 X-Date 标头。X-Date 标头可以通过调用 authorization.getDateString() 来检索

加密请求和响应

此库使客户端能够在 TLS 层之上建立受信任的加密会话,同时(且独立地)提供通过 HMAC+HKDF 样式身份验证来验证和识别客户端的能力。

此功能的原理包括但不限于

  1. 额外安全层的必要性
  2. 对网络或 TLS 本身缺乏信任(请参阅 https://blog.cloudflare.com/incident-report-on-memory-leak-caused-by-cloudflare-parser-bug/)
  3. 需要确保服务器为 HMAC+HKDF 身份验证提供的初始密钥材料 (IKM) 的机密性
  4. 需要确保用户提交给 API 进行身份验证的凭据的机密性

您可能想要与 API 本身建立加密会话的主要原因是确保 IKM 的机密性,以防止在不受信任的网络上发生数据泄漏,从而避免在类似 Cloudflare 的事件(或任何中间人攻击)中暴露信息。加密会话使您能够使用像 Cloudflare 这样的服务,即使再次发生内存泄漏,您也有信心 IKM 和其他安全数据不会被泄露。

生成密钥

要加密、解密、签名和验证消息,您需要能够生成适当的密钥。在内部,此库使用 libsodium-swift 来执行所有必要的密码学功能。

加密密钥

加密使用 sodium crypto box。密钥对可以按如下方式生成

let kp = Utils.generateKeypair()

签名密钥

加密使用 sodium 签名。密钥对可以按如下方式生成

let kp = Utils.generateSigningKeypair()

加密请求正文

有效负载可以按如下方式加密

import CryptoSwift // For .bytes alias

    let payload = """
{
    "foo": "bar"
}
"""

guard var Request = try? Request(
    secretKey: kp.secretKey
) else {
    // Handle init errors
}

guard let cipher = try? request!.encrypt(
    request: payload.data(using: .utf8, allowLossyConversion: false)!,
    publicKey: publicKey // 32 byte public key from server
) else {
    // Handle errors
}

// Do your HTTP request here

请注意,您需要拥有预先引导的公钥才能加密数据。对于 v1 API,这通常由 /api/v1/server/otk 返回。

解密响应

来自服务器的响应可以按如下方式解密

import CryptoSwift // For .bytes alias

guard let response = try? Response(
    secretKey: kp.secretKey
) else {
    // Handle initialization errors
}

guard let decrypted = try? response!.decrypt(
    response: Data(base64Encoded: "")!.bytes, // The raw body provided in the servers http response
) else {
    // Handle errors
}

V2 加密有效负载

版本 2 的工作方式与版本 1 有效负载相同,唯一的例外是解密消息所需的所有组件都捆绑在有效负载本身中,而不是分解为单独的标头。这减轻了开发人员管理多个标头的担忧。

版本 2 有效负载描述如下。每个组件连接在一起。

长度
4 字节标头 DE259002,二进制格式 4 字节
随机数 24 字节
与私钥关联的公钥 32 字节
加密正文 X 字节
签名公钥 32 字节
签名或原始请求正文 64 字节
先前连接在一起的元素的校验和 64 字节