RSA密钥、加密和数字签名

RSA密钥

由上一篇文章咱们能够知道,公钥是(e,n)、私钥是(d,n)。而在实际应用中,咱们接触到到的不是e、d、n,而是特定格式的数据或者文件。html

PKCS

PKCS 全称是 Public-Key Cryptography Standards(公钥加密标准),是由 RSA 实验室与其它安全系统开发商为促进公钥密码的发展而制订的一系列标准,PKCS 目前共发布过 15 个标准。其中比较经常使用的有:git

标准 名称 格式 简介
PKCS#1 RSA密码编译标准 / 定义了RSA的数理基础、公/私钥格式,以及加/解密、签/验章的流程。
PKCS #7 密码消息语法标准 / 参见RFC 2315。规范了以公开密钥基础设施(PKI)所产生之签名/密文之格式。其目的同样是为了拓展数字证书的应用。
PKCS#8 私钥消息表示标准 .p8 Apache读取证书私钥的标准。
PKCS#10 证书申请标准 .p10 .csr 参见RFC 2986。规范了向证书中心申请证书之CSR(certificate signing request)的格式。
PKCS#12 我的消息交换标准 .p12 .pfx 定义了包含私钥与公钥证书(public key certificate)的文件格式。私钥采密码(password)保护。

其中.csr或.certSigningRequest是证书请求格式,拿着这个请求文件向CA获取签名过的证书。譬如咱们在配置开发证书时候,先经过钥匙串生成.csr文件,而后上传,苹果根据.csr文件为咱们生成开发证书。github

pfx,p12文件是二进制格式,同时含私钥和证书,一般有保护密码。在钥匙串中因此能够展开的证书均可以导出p12。算法

X.509

X.509是常见通用的证书格式。全部的证书都符合为Public Key Infrastructure (PKI) 制定的 ITU-T X509 国际标准。数组

格式 编码形式
.der ASCII
.pem Base64
.cer 二进制
.crt 二进制
  • .cer/.crt是用于存放证书,它是2进制形式存放的,不含私钥。
  • pem文件通常是文本格式的,能够放证书或者私钥,或者二者都有
  • pem若是只含私钥的话,通常用.key扩展名,并且能够有密码保护

ASN.1格式

ASN.1格式在RSA密钥证书中,有举足轻重的地位。上面咱们提到的因此证书格式p十二、pfx、cer,都是ASN.1格式的。将pem中base64串编码,获得的公司钥实体数据也是ASN.1格式的。安全

在电信和计算机网络领域,ASN.1(Abstract Syntax Notation One) 是一套标准,是描述数据的表示、编码、传输、解码的灵活的记法。它提供了一套正式、无歧义和精确的规则以描述独立于特定计算机硬件的对象结构。bash

关于它的语法数据类型等详细介绍,请参看这篇文章网络

咱们来看ASN.1的基本编码规则。ASN.1编码的数据大体分为三个部分,标签(tag)字段+长度(Length)字段+值(Value)字段数据结构

标签(tag)字段:关于标签类别和编码格式的信息。app

长度(Length)字段:定义内容字段的长度(字节数)。

值(Value)字段:包含实际的数据 。

标签字段(标头)表示了不一样的值的数据类型。常见的标头有

标签字段 数据类型 示例
0x01 布尔值 true表示为 0x01 01 FF;false表示为 0x01 01 00
0x02 整型 16位整形数的9表示为 0x02 02 0009
0x03 位串(bit string)
0x04 八位串(octor string)
0x05 空值 nil 表示为 0x05 00
0x13(19) 可打印的ASCII编码字符串
0x16(22) ASCII编码字符串
0x31 数组 [3,5]表示为 0x31 06 0x02 01 03 0x02 01 05

RSA密钥的结构

下面咱们来看看,公私钥匙到底长什么样子。n、e、d都是怎样存放的。

为了研究方便,咱们先用openssl生成一个1024位的RSA私钥

openssl genrsa -out private-key-1024.pem 1024
复制代码

导出公钥

openssl rsa -in private-key-1024.pem -pubout -out public-key-1024.pem
复制代码

公钥数据的结构

pem格式包含的是base64编码的数据。咱们取出其中字符串。而后取出首尾标识符及回车符,base64反编码获得ASN.1格式的二进制数据。

公钥的ASN.1结构为

RSAPublicKey :: = SEQUENCE{
	 modulus         INTEGER  n (模长,正整数)
	 publicExponent  INTEGER  e (公钥指数)
}
复制代码

咱们取出公钥字符串,而后base64解码,获得34字节数据。他的大体结构以下

0x30 --标头,0x30表示序列类型
0x81 --内容较长,将用后面1(0x80 - 0b10000000)个字节标识长度
0x9f --包含159个字节长度的内容
   
   0x30  --标头,0x30表示序列类型
   0x0d  --数据长度,后面包含13个字节数据
   
	   0x06 --标头,6表示对象标识符
	   0x09 --9个字节
	   // oid值 1.2.840.113549.1.1.1 (rsaEncryption) 
	   0x2a 0x86 0x48 0x86 0xf7 0x0d 0x01 0x01 0x01 
	   
	   0x05 0x00 -- null
 
   0x03 --标头,03表示bitstring位串
   0x81 
   0x8d --141字节长度
   
   
       0x00  --bitstring开头
       0x30  --标头,0x30表示
       0x81  
       0x89  --137个字节长度
    
           0x02  --模长n的标头,2表示整数
           0x81  
           0x81  --129字节
           
           // 模长n的值,129字节存储,128个有效字节
           0x00 
           0xa0 0x29 0xbf 0xd0 0x38 0xfc 0xeb 0xbb 
           0xba 0xa9 0x09 0x90 0x7c 0x34 0xeb 0x9b 
           0xd8 0x61 0x73 0x11 0xd1 0x28 0x49 0x39 
           0xb8 0x43 0xe1 0xc2 0x1e 0xa2 0x87 0x20 
           0x19 0x5c 0xf1 0x50 0x88 0xb2 0x63 0xc0 
           0xd5 0x2b 0x68 0x88 0x52 0x75 0xcd 0xd8 
           0x26 0xba 0xb4 0x30 0x69 0xe0 0xa4 0xe9 
           0xe0 0x3d 0xcf 0xbf 0x67 0xa7 0x98 0xb1 
           0xbe 0x20 0x41 0x73 0x5b 0xe6 0xf0 0x7a 
           0x92 0x41 0x1b 0x62 0x57 0x47 0x60 0x25 
           0xbe 0x3b 0x75 0xed 0x46 0x0e 0x61 0x52 
           0x03 0xa5 0x00 0x59 0x1c 0x3c 0x94 0xd2 
           0x94 0x16 0xbc 0x08 0x6d 0x4f 0xba 0x86 
           0xc6 0xfc 0xd2 0x3c 0x79 0xc4 0x99 0x17 
           0xaf 0xf9 0x5c 0x99 0x50 0xe7 0x28 0x2a 
           0x42 0xd9 0xc7 0x2e 0xba 0x17 0x9c 0x23  
 
           0x02            -- e的标头
           0x03            -- e长度为3个字节
           0x01 0x00 0x01  -- 公钥指数e
复制代码

公钥的pem数据中,主要包含两部份内容,第一部分是OID值,第二部分是公钥数据实体。

OID值(Object Identifier 对象标识符)为1.2.840.113549.1.1.1,它表示PKCS1公钥加密标识符。

其中,各个数字按顺序表示为

  • 1 --ISO标准
  • 2 --member-body(成员主体)
  • 840 --US 美国
  • 113549 --RSADSI
  • 1 --PKCS
  • 1 --PKCS的第一个标准,即PKCS#1
  • 1 --rsaEncryption RSA加密

你能够在这里这里查看他的详细介绍

值得注意的是:

  • 当ASN.1的长度字段较大时,会以多个字节表示长度。在上述数据中,长度字段0x81&0b10000000=1,因此0x81不表示长度,而是其后0x81-0b10000000=1个字节表示长度
  • 当整数类型最高位为1时,会在最前面补0x00。上述数据中,1024位模长对应128字节的n,而前面的0x00不包含在n当中
  • e的值0x010001(65537),是一个固定值,不管用何种方式生成,模长是多少位的,e的值都是同样的。这是加密性能和安全的兼顾。同时因为e同样,因此模长的变化并不会增长加密的时间复杂度。
  • 实际有效的公钥数据,是最后bitstring部分的值

私钥长什么样

私钥的ASN.1结构为

RSAPrivateKey :: = SEQUENCE{
     version            Version,

     modulus            INTEGER,   ------ n
     publicExponent     INTEGER,   ------ e
     privateExponent    INTEGER,   ------ d
     prime1             INTEGER,   ------ p
     prime2             INTEGER,   ------ q
     exponent1          INTEGER,   ------ d mod (p -1)
     exponent2          INTEGER,   ------ d mod (q -1)
     coefficient        INTEGER,   ------- (inverse of q) mod p
     otherPrimeInfos    OtherPrimeInfos   ------ OPTIONAL(当version为0时,不存在;当 version为1时,必须有)
 }

 Version :: = INTEGER{ two-prime(0), multi(1)}
复制代码

值得注意的是私钥文件里边,不可是包含实际有效私钥(e,n),他还包含公钥指数,咱们在密钥生成中用到的p、q,以及其余一些信息。这也是咱们能够经过私钥导出公钥的缘由。

咱们将上面的到的私钥字符串base64反编码以后,的到的数据结构以下:

0x30 --标头,序列类型
0x82 --后面2个字节表示长度
0x02 0x5c --数据长度45

    0x02
    0x01
    0x00  --版本号version为0

    0x02
    0x81
    0x81 --129个字节
    // 模数n
    0x00 
    0xa0 0x29 0xbf 0xd0 0x38 0xfc 0xeb 0xbb 
    0xba 0xa9 0x09 0x90 0x7c 0x34 0xeb 0x9b 
    0xd8 0x61 0x73 0x11 0xd1 0x28 0x49 0x39 
    0xb8 0x43 0xe1 0xc2 0x1e 0xa2 0x87 0x20 
    0x19 0x5c 0xf1 0x50 0x88 0xb2 0x63 0xc0 
    0xd5 0x2b 0x68 0x88 0x52 0x75 0xcd 0xd8 
    0x26 0xba 0xb4 0x30 0x69 0xe0 0xa4 0xe9 
    0xe0 0x3d 0xcf 0xbf 0x67 0xa7 0x98 0xb1 
    0xbe 0x20 0x41 0x73 0x5b 0xe6 0xf0 0x7a 
    0x92 0x41 0x1b 0x62 0x57 0x47 0x60 0x25 
    0xbe 0x3b 0x75 0xed 0x46 0x0e 0x61 0x52 
    0x03 0xa5 0x00 0x59 0x1c 0x3c 0x94 0xd2 
    0x94 0x16 0xbc 0x08 0x6d 0x4f 0xba 0x86 
    0xc6 0xfc 0xd2 0x3c 0x79 0xc4 0x99 0x17 
    0xaf 0xf9 0x5c 0x99 0x50 0xe7 0x28 0x2a 
    0x42 0xd9 0xc7 0x2e 0xba 0x17 0x9c 0x23 
    
    
    0x02
    0x03
    0x01 0x00 0x01   --公钥指数e
 
    0x02
    0x81
    0x80 --128个字节
    // 私钥质数d
	0x79 0x69 0xcc 0xb7 0xbb 0x4b 0xb8 0x24 
	0x32 0xc7 0x4b 0xb1 0xd5 0x06 0x85 0x09 
	0x3a 0x49 0xfd 0x62 0x27 0x4d 0x43 0xdd 
	0x56 0x9b 0x56 0xfb 0xc2 0x1f 0x71 0x11 
	0xdb 0x48 0x42 0xc2 0xcb 0x2d 0x78 0x43 
	0x49 0x15 0xc4 0x03 0x7b 0x87 0x44 0x49 
	0x34 0x6a 0xda 0x87 0xcc 0xeb 0x77 0xf8 
	0xb7 0x7e 0x04 0x0b 0xd4 0x37 0x0f 0x9f 
	0x92 0xd6 0x31 0xd7 0x4f 0x90 0xa0 0x8e 
	0x07 0x1a 0xf7 0x0d 0x79 0x25 0xf6 0x1a 
	0x0a 0x83 0x6b 0x00 0x33 0xbd 0x32 0x2c 
	0xb3 0xdd 0x71 0x64 0xb5 0xf8 0xcc 0x9f 
	0x21 0xc3 0x81 0xad 0xab 0xb0 0x1f 0x92 
	0x0b 0xed 0x88 0x76 0x6c 0x95 0xc6 0xe2 
	0xe7 0x28 0x24 0xca 0xa0 0x85 0xc7 0x69 
	0xc2 0x56 0xa2 0x4d 0x70 0x4b 0x59 0xe9 
	
	
    0x02 
    0x41 --65字节
    // 质数p值,有效64字节
    0x00
    0xd5 0x5f 0x27 0xc6 0x84 0xf4 0x37 0xda 
    0xa8 0x10 0x28 0x0f 0x33 0x8f 0x05 0xe7 
    0xa8 0xd3 0x09 0x7f 0xca 0x71 0xfe 0x86 
    0xa0 0x95 0xb3 0x21 0x30 0xb8 0xb4 0xcf 
    0x27 0x89 0x21 0xea 0x6d 0xcd 0xaf 0x34 
    0x2f 0x6d 0x3b 0x64 0xd6 0x41 0x85 0x74 
    0x10 0xd1 0x63 0x29 0xaa 0xf2 0x79 0xc0 
    0x4b 0xed 0x2c 0xf9 0x7b 0x7c 0x43 0x0f

    0x02
    0x41
    // 质数q值,有效64字节
    0x00 
    0xc0 0x29 0x40 0x7a 0x96 0x32 0x89 0xf7 
    0x97 0xbd 0x76 0xa3 0x6c 0xea 0x1b 0x7d 
    0xa4 0x23 0xe3 0x3d 0x4e 0x08 0x1a 0x21 
    0x10 0x48 0x81 0xed 0x29 0x01 0xc5 0xae 
    0xba 0xb9 0x5f 0x98 0x55 0xf4 0x24 0x9c 
    0xb0 0x14 0x97 0xde 0x34 0x07 0x4d 0x5e 
    0x53 0x5b 0x6b 0xc2 0x4d 0xcd 0xaf 0x46 
    0xde 0x9d 0xb8 0x06 0xfd 0x41 0x05 0xad
 
    ......
    
复制代码

值得注意的是,p和q都是模长的一半,64字节,512位。私钥质数d,长度和模长一致,都是128字节,1024位。因为私钥指数d很大,因此解密时耗费的计算力是比较大的。

公私钥的导入

在加密或签名以前,咱们须要将上面所说的密钥文件转化为咱们的密钥对象。咱们一般采用系统的Security框架进行加密,与之对应的。咱们须要读取密钥文件并生成SecKey

pem文件的导入

pem是咱们最为常见的存储RSA密钥的文件格式。

导入pem密钥时咱们须要取出pem中的开始结束标识,再进行base64解密获得密钥data。

而后经过data生成SecKey

let keyClass = type == .public ? kSecAttrKeyClassPublic : kSecAttrKeyClassPrivate
let sizeInBits = data.count * 8
let keyDict: [CFString: Any] = [
    kSecAttrKeyType: kSecAttrKeyTypeRSA,
    kSecAttrKeyClass: keyClass,
    kSecAttrKeySizeInBits: NSNumber(value: sizeInBits),
    kSecReturnPersistentRef: true
]
    
var error: Unmanaged<CFError>?
guard let key = SecKeyCreateWithData(data as CFData, keyDict as CFDictionary, &error) else {
    print(error?.takeRetainedValue() ?? "unkown error")
    return nil
}
复制代码

公私钥惟一区别是,KeyClass,公钥时传kSecAttrKeyClassPublic,而私钥是kSecAttrKeyClassPrivate

p12/pfx文件导入

咱们从文件读取p12文件,获得数据data,而后再用data建立SecKey

var item = CFArrayCreate(nil, nil, 0,nil)
let options = pwd != nil ? [kSecImportExportPassphrase:pwd] : [:]
let status = SecPKCS12Import(data as CFData,options as CFDictionary,&item)
if status != noErr {
    return nil
}
    
guard  let itemArr = item as? [Any],
    let dict = itemArr.first as? [String:Any],
    let secIdentity = dict[kSecImportItemIdentity as String]   else{
    return nil
}
    
let secIdentityRef = secIdentity as! SecIdentity
var keyRef : SecKey?
SecIdentityCopyPrivateKey(secIdentityRef,&keyRef)
复制代码

上述代码中的keyRef就是咱们获取到的私钥对象。

由于私钥中包含了公钥的因此信息,咱们也能够经过私钥keyRef导出公钥

let pubKey1 = SecKeyCopyPublicKey(keyRef)
复制代码

但这样作是毫无心义的,由于当咱们拿到p12/pfx时,就意味着咱们拿到的是私钥。对于客户端来讲是要拿来最数据签名的。若是要作数据加密,咱们拿到得将是指包含公钥的pem文件。

RSA加密与解密

Padding

在进行RSA加密以前,咱们还须要理解一个重要的概念:padding

为了提升RSA加密的安全性,加密以前每每会在明文前面加上一段包含随机数的padding。加入padding以后的数据结构以下:

EM = 0x00 || 0x02 || PS || 0x00 || M.
复制代码
  • PS(padding string),随机数
  • M,明文

咱们知道RSA是分块加密的,而若是有padding,每块还必须减去一部分长度

padding 方式 模长(字节) 每段明文最大长度(字节) 每段密文长度
no padding n n n
PKCS1 n n-11 n
OAEP n n-42 n

加密的实现

先获取到模长,根据padding计算分块最大长度

// 模长
let blockSize = SecKeyGetBlockSize(key)

// 数据分块的最大长度
var maxChunkSize : Int
switch padding {
case .PKCS1:
    maxChunkSize = blockSize - 11
case .OAEP:
    maxChunkSize = blockSize - 42
case []: // no padding
    maxChunkSize = blockSize
default: // default PKCS1
    maxChunkSize = blockSize - 11
}
复制代码

对数据进行分块加密

var retData = Data()
var idx = 0

while idx < data.count {
    let endIdx = min(idx+maxChunkSize,data.count)
    var chunkData = [UInt8](data[idx..<endIdx])
    var outLen    = blockSize;
    let outBuf    = UnsafeMutablePointer<UInt8>.allocate(capacity:outLen)
    defer { outBuf.deallocate() }
    
    var status = noErr;
    status = SecKeyEncrypt(key,
                           padding,
                           &chunkData,
                           chunkData.count,
                           outBuf,
                           &outLen)
    guard  status == noErr else {
        print("SecKeyEncrypt fail. Error Code: \(status)")
        return nil;
    }
    
    retData.append(UnsafeBufferPointer(start:outBuf, count:outLen))
    idx += maxChunkSize
}
复制代码

其中,核心方法是SecKeyEncrypt。输入公钥key,padding类型,以及当前分块数据chunkData,输出outBuf

须要注意的是,须要同时知足如下三点,不然加密失败。

  • key必须是公钥;
  • chunkData长度必须与padding匹配不能过长;
  • outLen虽然是inout类型,但必须等于模长;

还有就是为了传输方便,通常会将data转化为base64字符串

let ret = retData.base64EncodedString()
复制代码

值得注意的是,因为padding的存在,咱们对同一数据进行屡次加密,每次加密获得的结果都是不同的。可是这并不会影响解密的结果,由于padding后的数据结构是固定的,成功解密以后会自动去除无效的数据。

解密的实现

咱们拿到的加密数据,通常是base64字符串。咱们须要先将其转化为data再base64解码

let data = Data(base64Encoded:string, options:.ignoreUnknownCharacters)
复制代码

跟加密相似的,解码咱们用到SecKeyDecrypt方法,具体实现以下:

let blockSize = SecKeyGetBlockSize(key)
var retData = Data()
var idx = 0
while idx < data.count {
    let endIdx = min(idx+blockSize,data.count)
    var chunkData = [UInt8](data[idx..<endIdx])
    var outLen    = blockSize;
    let outBuf    = UnsafeMutablePointer<UInt8>.allocate(capacity:outLen)
    defer { outBuf.deallocate() }
    
    var status = noErr;
    status = SecKeyDecrypt(key,
                           padding,
                           &chunkData,
                           chunkData.count,
                           outBuf,
                           &outLen)
    guard  status == noErr else {
        print("SecKey decrypt fail. Error Code: \(status)")
        return nil;
    }
    
    let ret1 = UnsafeBufferPointer(start:outBuf, count:outLen)
    retData.append(ret1)
    idx += blockSize
}
复制代码

值得注意的是:

  • key必须是私钥
  • 解密也须要分块进行,每块长度都等于模长
  • outLen虽然是inout的值,解码完以后会变成实际获得的明文长度。但它的初始值不能小于明文长度,不然解密失败。咱们取模长值是最稳妥的作法。

RSA签名和认证

通常在作数字签名是,每每不是直接用私钥对明文进行签名。而是将明文进行模中散列函数运算后,对消息摘要进行签名。

在验证的时候,若是用公钥可以对签名进行解密,说明发送者身份没有被仿冒。而后对明文进行散列函数运算获得的摘要与解密的摘要对比,若是一致证实消息和消息摘要在传送过程当中都没有被串改。

不一样的散列函数,对应不一样的padding值

散列函数 签名算法 pading
MD5 MD5WithRSA PKCS1MD5
SHA1 SHA1WithRSA PKCS1SHA1
SHA224 SHA224WithRSA PKCS1SHA224
SHA256 SHA256WithRSA PKCS1SHA256
SHA384 SHA384WithRSA PKCS1SHA384
SHA512 SHA512WithRSA PKCS1SHA512

签名的实现

先对原始数据进行散列函数运算

var digestData : Data
switch pading {
case .PKCS1MD5:
    digestData = DigestUtil.md5(data:data)
case .PKCS1SHA1:
    digestData = DigestUtil.sha1(data:data)
case .PKCS1SHA1:
    digestData = DigestUtil.sha1(data:data)
case .PKCS1SHA224:
    digestData = DigestUtil.sha224(data:data)
case .PKCS1SHA256:
    digestData = DigestUtil.sha256(data:data)
case .PKCS1SHA384:
    digestData = DigestUtil.sha384(data:data)
case .PKCS1SHA512:
    digestData = DigestUtil.sha512(data:data)
default:
    digestData = data
}
复制代码

对消息摘要进行签名

let blockSize = SecKeyGetBlockSize(key)
var maxChunkSize : Int = blockSize - 11
var retData = Data()
var idx = 0
while idx < digestData.count {
    let endIdx = min(idx+maxChunkSize,digestData.count)
    var chunkData = [UInt8](digestData[idx..<endIdx])
    var outLen = SecKeyGetBlockSize(key);
    let outBuf = UnsafeMutablePointer<UInt8>.allocate(capacity:outLen)
    defer { outBuf.deallocate() }
    var status = noErr;
    status = SecKeyRawSign(key,
                           pading,
                           &chunkData,
                           chunkData.count,
                           outBuf,
                           &outLen)
    if status == noErr {
        let ret1 = UnsafeBufferPointer(start:outBuf, count:outLen)
        retData.append(ret1)
    }else {
        print("SecKey sign fail. Error Code: \(status)")
        return nil;
    }
    idx += maxChunkSize
}
复制代码

能够看到它和加密的实现是很像的,并且还采用了和PKCS1相似的padding

standard ASN.1 padding will be done, as well as PKCS1 padding

能够发现还有两种SecPadding咱们没有提到,sigRawPKCS1MD2sigRaw是DSA算法的,使用的不多。PKCS1MD2安全性很低,基本已没人使用。

须要注意的是,因为散列运算以后的结果都是一致的,而即使是长度最大的SHA512也只有64个字节,远远小于RSA签名117字节的最大块长度。因此咱们获得的结果,都是128字节。而且屡次签名获得的结果都是一致的。

验证的实现

验证以前咱们先对原始数据进行与签名相同散列运算,获得摘要digestData

var digestData : Data
switch pading {
case .PKCS1MD5:
    digestData = DigestUtil.md5(data:data)
case .PKCS1SHA1:
    digestData = DigestUtil.sha1(data:data)
case .PKCS1SHA1:
    digestData = DigestUtil.sha1(data:data)
case .PKCS1SHA224:
    digestData = DigestUtil.sha224(data:data)
case .PKCS1SHA256:
    digestData = DigestUtil.sha256(data:data)
case .PKCS1SHA384:
    digestData = DigestUtil.sha384(data:data)
case .PKCS1SHA512:
    digestData = DigestUtil.sha512(data:data)
default:
    digestData = data
}
复制代码

而后咱们输入公钥、padding、明文摘要、签名,获得验证是否成功的结果。

var digestBuf = [UInt8](digestData)
let signBuf   = [UInt8](signData)
var status = noErr;
status = SecKeyRawVerify(key,
                         pading,
                         &digestBuf,
                         digestBuf.count,
                         signBuf,
                         signBuf.count)
                         
if status == errSecSuccess {
    return true
} else {
    return false
}
复制代码

能够看到,代码中不存在循环语句。由于签名数据、摘要数据都是固定长度,而且小于等于模长。因此没有分段验证的说法。

若是想看完整的实现,请看这里

参考资料

相关文章
相关标签/搜索