在 iOS/Swift 中创建并导出为 Base64 的 RSA 公钥在 Java 中无法识别

2023-11-24

TL;DR:在 iOS 中生成并存储在钥匙串中、导出为 base64 并发送到 java 后端的 RSA 公钥无法识别。

我正在 iOS 应用程序中实现聊天加密功能,并使用对称 + 非对称密钥来处理它。

无需过多讨论细节,在后端,我使用用户的公钥来加密用于加密和解密消息的对称密钥。

我创建了两个框架,分别用 Swift 和 Java(后端)来处理密钥生成、加密、解密等。我也对它们进行了测试,所以我 100% 一切都按预期工作。

但是,后端似乎无法识别从 iOS 传递的公钥的格式。两边都使用 RSA,这是我在 Swift 中用来生成密钥的代码:

// private key parameters
static let privateKeyParams: [String : Any] = [
        kSecAttrIsPermanent as String: true,
        kSecAttrApplicationTag as String: "..." // I have a proper unique tag here
]

// public  key parameters
static let publicKeyParams: [String : Any] = [
        kSecAttrIsPermanent as String: true,
        kSecAttrApplicationTag as String: "..." // I have a proper unique tag here
]

// global parameters for our key generation
static let keyCreationParameters: [String : Any] = [
        kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
        kSecAttrKeySizeInBits as String: 2048,
        kSecPublicKeyAttrs as String: publicKeyParams,
        kSecPrivateKeyAttrs as String: privateKeyParams
]

...

var publicKey, privateKey: SecKey?
let status = SecKeyGeneratePair(Constants.keyCreationParameters as CFDictionary, &publicKey, &privateKey)

我使用镜面代码从钥匙串中读取钥匙。

这是我用来将公钥导出为 base64 字符串的代码:

extension SecKey {
  func asBase64() throws -> String {
    var dataPtr: CFTypeRef?
    let query: [String:Any] = [
      kSecClass as String: kSecClassKey,
      kSecAttrApplicationTag as String: "...", // Same unique tag here
      kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
      kSecReturnData as String: kCFBooleanTrue
    ]
    let result = SecItemCopyMatching(query as CFDictionary, &dataPtr)

    switch (result, dataPtr) {
    case (errSecSuccess, .some(let data)):
      // convert to Base64 string
      let base64PublicKey = data.base64EncodedString(options: [])
      return base64PublicKey
    default:
      throw CryptoError.keyConversionError
    }
  }
}

在后端级别,我使用以下 Java 代码将 base64 字符串转换为公钥:

public PublicKey publicKeyFrom(String data) throws NoSuchAlgorithmException, InvalidKeySpecException {
    byte[] publicBytes = Base64.decodeBase64(data);
    X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicBytes);
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    return keyFactory.generatePublic(keySpec);
}

但这在最后一行失败了,但有一个例外:

java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException: algid parse error, not a sequence

进行一些手动调试后,我注意到公钥的格式不同 - 当我在 iOS 中生成密钥然后导出为 Base 64 时,它看起来像这样:

MIIBCgKCAQEA4M/bRDdH0f6qFIXxOg13RHka+g4Yv8u9PpPp1IR6pSwrM1aq8B6cyKRwnLe/MOkvODvDfJzvGXGQ01zSTxYWAW1B4uc/NCEemCmZqMosSB/VUJdNxxWtt2hJxpz06hAawqV+6HmweAB2dUn9tDEsQLsNHdwYouOKpyRZGimcF9qRFn1RjR0Q54sUh1tQAj/EwmgY2S2bI5TqtZnZw7X7Waji7wWi6Gz88IkuzLAzB9VBNDeV1cfJFiWsZ/MIixSvhpW3dMNCrJShvBouIG8nS+vykBlbFVRGy3gJr8+OcmIq5vuHVhqrWwHNOs+WR87K/qTFO/CB7MiyiIV1b1x5DQIDAQAB

总共 360 个字符,而在 Java 中执行相同的操作(仍然使用 RSA),则类似于:

MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCAAnWO4BXUGP0qM3Op36YXkWNxb4I2pPZuZ7jJtfUO7v+IO1mq43WzNaxLqqLPkTnMrv2ACRDK55vin+leQlL1z0LzVxjtZ9F6pajQo1r7PqBlL5N8bzBFKpagEf0QfyHPw0/0kG9DMnvQ+Im881QyN2zdl33wp5Fi+jRT7cunFQIDAQAB

长度为216个字符。

我无法弄清楚出了什么问题 - 显然,如果 iOS 以不同的键处理键,并且需要特殊处理才能与其他人交谈,我不会感到惊讶。

任何想法?


当将 iOS 应用程序连接到 Java 后端时,我们遇到了完全相同的问题。还有加密导出导入管理器提到的pedrofb也帮助了我们,这太棒了。然而,代码中的CryptoExportImportManager类有点复杂,可能难以维护。这是因为在向 DER 编码添加新组件时使用自上而下的方法。因此,必须提前计算长度字段包含的数字(即在定义长度适用的内容之前)。因此,我创建了一个新类,我们现在用它来转换 RSA 公钥的 DER 编码:

class RSAKeyEncoding: NSObject {

  // ASN.1 identifiers
  private let bitStringIdentifier: UInt8 = 0x03
  private let sequenceIdentifier: UInt8 = 0x30

  // ASN.1 AlgorithmIdentfier for RSA encryption: OID 1 2 840 113549 1 1 1 and NULL
  private let algorithmIdentifierForRSAEncryption: [UInt8] = [0x30, 0x0d, 0x06,
    0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00]

  /// Converts the DER encoding of an RSA public key that is either fetched from the
  /// keychain (e.g. by using `SecItemCopyMatching(_:_:)`) or retrieved in another way
  /// (e.g. by using `SecKeyCopyExternalRepresentation(_:_:)`), to a format typically
  /// used by tools and programming languages outside the Apple ecosystem (such as
  /// OpenSSL, Java, PHP and Perl). The DER encoding of an RSA public key created by
  /// iOS is represented with the ASN.1 RSAPublicKey type as defined by PKCS #1.
  /// However, many systems outside the Apple ecosystem expect the DER encoding of a
  /// key to be represented with the ASN.1 SubjectPublicKeyInfo type as defined by
  /// X.509. The two types are related in a way that if the SubjectPublicKeyInfo’s
  /// algorithm field contains the rsaEncryption object identifier as defined by
  /// PKCS #1, the subjectPublicKey field shall contain the DER encoding of an
  /// RSAPublicKey type.
  ///
  /// - Parameter rsaPublicKeyData: A data object containing the DER encoding of an
  ///     RSA public key, which is represented with the ASN.1 RSAPublicKey type.
  /// - Returns: A data object containing the DER encoding of an RSA public key, which
  ///     is represented with the ASN.1 SubjectPublicKeyInfo type.
  func convertToX509EncodedKey(_ rsaPublicKeyData: Data) -> Data {
    var derEncodedKeyBytes = [UInt8](rsaPublicKeyData)

    // Insert ASN.1 BIT STRING bytes at the beginning of the array
    derEncodedKeyBytes.insert(0x00, at: 0)
    derEncodedKeyBytes.insert(contentsOf: lengthField(of: derEncodedKeyBytes), at: 0)
    derEncodedKeyBytes.insert(bitStringIdentifier, at: 0)

    // Insert ASN.1 AlgorithmIdentifier bytes at the beginning of the array
    derEncodedKeyBytes.insert(contentsOf: algorithmIdentifierForRSAEncryption, at: 0)

    // Insert ASN.1 SEQUENCE bytes at the beginning of the array
    derEncodedKeyBytes.insert(contentsOf: lengthField(of: derEncodedKeyBytes), at: 0)
    derEncodedKeyBytes.insert(sequenceIdentifier, at: 0)

    return Data(derEncodedKeyBytes)
  }

  private func lengthField(of valueField: [UInt8]) -> [UInt8] {
    var length = valueField.count

    if length < 128 {
      return [ UInt8(length) ]
    }

    // Number of bytes needed to encode the length
    let lengthBytesCount = Int((log2(Double(length)) / 8) + 1)

    // First byte encodes the number of remaining bytes in this field
    let firstLengthFieldByte = UInt8(128 + lengthBytesCount)

    var lengthField: [UInt8] = []
    for _ in 0..<lengthBytesCount {
      // Take the last 8 bits of length
      let lengthByte = UInt8(length & 0xff)
      // Insert them at the beginning of the array
      lengthField.insert(lengthByte, at: 0)
      // Delete the last 8 bits of length
      length = length >> 8
    }

    // Insert firstLengthFieldByte at the beginning of the array
    lengthField.insert(firstLengthFieldByte, at: 0)

    return lengthField
  }
}

Usage

您可以在函数中使用此类asBase64()像这样:

extension SecKey {
  func asBase64() throws -> String {
    var dataPtr: CFTypeRef?
    let query: [String:Any] = [
      kSecClass as String: kSecClassKey,
      kSecAttrApplicationTag as String: "...", // Same unique tag here
      kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
      kSecReturnData as String: kCFBooleanTrue
    ]
    let result = SecItemCopyMatching(query as CFDictionary, &dataPtr)

    switch (result, dataPtr) {
    case (errSecSuccess, .some(let data)):

      // convert to X509 encoded key
      let convertedData = RSAKeyEncoding().convertToX509EncodedKey(data)

      // convert to Base64 string
      let base64PublicKey = convertedData.base64EncodedString(options: [])
      return base64PublicKey
    default:
      throw CryptoError.keyConversionError
    }
  }
}

更新 - 其他问题

使用上面的类一段时间后,我们偶然发现了另一个问题。有时,从钥匙串中获取的公钥似乎无效,因为由于某种原因它的大小增大了。此行为与问题中描述的结果相匹配(尽管在我们的示例中,Base64 编码密钥的大小已增长到 392 个字符,而不是 360 个字符)。不幸的是,我们没有找到这种奇怪行为的确切原因,但我们找到了两种解决方案。第一个解决方案是指定kSecAttrKeySizeInBits随着kSecAttrEffectiveKeySize定义查询时,如下面的代码片段所示:

let keySize = ... // Key size specified when storing the key, for example: 2048

let query: [String: Any] = [
    kSecAttrKeySizeInBits as String: keySize,
    kSecAttrEffectiveKeySize as String: keySize,
    ... // More attributes
]

var dataPtr: CFTypeRef?

let result = SecItemCopyMatching(query as CFDictionary, &dataPtr)

第二种解决方案是始终从钥匙串(如果有)中删除旧密钥,然后再添加具有相同标签的新密钥。

更新 - 替代解决方案

我发表了这个项目在 GitHub 上,它可以用作上述类的替代品。

参考

ASN.1、BER 和 DER 子集的外行指南

RFC 5280(X.509 v3)

RFC 8017(PKCS #1 v2.2)

我找到的一些代码here在创作时给了我灵感lengthField(...)功能。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 iOS/Swift 中创建并导出为 Base64 的 RSA 公钥在 Java 中无法识别 的相关文章

  • java项目中无法加载类“org.slf4j.impl.StaticLoggerBinder”错误? [复制]

    这个问题在这里已经有答案了 我越来越Failed to load class org slf4j impl StaticLoggerBinder 错误 我想将记录器写入文件 所以我使用了 log4j jar 并使用 apache tomca
  • 但是创建静态实用方法不应该被过度使用吗?如何避免呢? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 随着时间的推移 java项目中引入了许多实用方法来完成更复杂和简单的任务 当使用静态方法时 我们在代码中引入了紧密耦合 这使得我们的代
  • for循环中更新JLabel的问题

    我的程序的想法是从之前在其他 JFrame 中保存的列表中选择一个名称 我想在标签中一个接一个地打印所有名称 它们之间有很小的延迟 然后停在其中一个名称上 问题是lbl setText String 如果有多个则不起作用setText co
  • Java 中如何验证字符串的格式是否正确

    我目前正在用 Java 编写一个验证方法来检查字符串是否是要更改为日期的几种不同格式之一 我希望它接受的格式如下 MM DD YY M DD YY MM D YY 和 M D YY 我正在测试第一种格式 每次它都告诉我它无效 即使我输入了有
  • 如何在 Eclipse 中获得完全限定的类名?

    有没有一种快速方法可以在 Eclipse 中单击 Java 类并获取其完全限定名称 或将其复制到剪贴板 2016年6月29日编辑 正如 Jeff 所指出的 您只需要执行以下第二步 1 Double click on the class na
  • 自动生成的 Swift 桥接标头中“找不到接口声明”

    我当前的项目包含 Swift 和 Objective C 代码 两种类型的源文件都使用另一种语言的代码 当我进行完全清理并重新编译时 几乎每个 Swift 类声明都出现错误Module Swift h 形式为 Cannot find int
  • ActiveMQ JNDI 查找问题

    尝试使用 JNDI 运行以下 ActiveMQ http activemq apache org jndi support html http ActiveMQ 20JNDI 并且我的 jboss server node lib 文件夹中有
  • jDBI中如何进行内查询?

    我怎样才能在 jDBI 中执行这样的事情 SqlQuery select id from foo where name in
  • JPA Web 应用程序管理策略

    我们目前正在开发一个 J2EE Web 应用程序 使用 JPA 作为我们的数据访问层 我们目前正在研究几种不同的策略来在我们的应用程序中利用缓存 Create an EntityManager per request 在请求范围内获取缓存
  • iOS:如何创建核心数据库的备份副本?以及如何导出/导入该副本?

    我想为我的应用程序的用户提供创建核心数据数据库备份的可能性 特别是在他切换到新设备等情况下 我该怎么做呢 特别是如何重新导入该文件 我的意思是 假设他制作了数据库的备份副本 然后更改了大量内容并想要重置为以前保存的备份副本 我该怎么做呢 T
  • 为什么 java.util.Arraylist#clear 按照 OpenJDK 中的方式实现?

    http grepcode com file repository grepcode com java root jdk openjdk 6 b14 java util ArrayList java 473 http grepcode co
  • 如何使用eclipse调试JSP tomcat服务?

    我想使用 Eclipse IDE 调试器来调试单独运行的 JSP Struts Tomcat Hibernate 应用程序堆栈 如何设置 java JVM 和 eclipse 以便设置断点 监视变量值并查看当前正在执行的代码 我刚刚用谷歌搜
  • 隐藏 UITableview 单元格

    我正在尝试从 UITableView 中隐藏单元格 就像删除操作一样 但我只想隐藏它以便稍后在相同位置显示它 我知道 UITableViewCell 有一个名为 隐藏 的属性 但是当我使用此属性隐藏单元格时 它会隐藏但没有动画 并且会留下空
  • 在java中执行匿名pl/sql块并获取结果集

    我想执行匿名 PL SQL 并需要获取结果集对象 我得到了可以通过在 PL SQL 块内使用游标来完成的代码 但 PL SQL 块本身将以文本形式来自数据库 所以我无法编辑该 PL SQL 块 并且它只会返回两个值 其列名始终相同 它将返回
  • 检查按钮是否可用?如果没有,请等待 5 秒钟,然后再次检查?

    基本上我想看看此刻是否可以单击按钮 如果没有我想再试一次 所以我需要某种 goto 函数来返回到代码的前一行 尽管我怀疑我写得非常糟糕 但它本来可以做得更容易 try driver findElement By xpath button i
  • Java 中序列化的目的是什么?

    我读过很多关于序列化的文章 以及它如何如此美好和伟大 但没有一个论点足够令人信服 我想知道是否有人能真正告诉我通过序列化一个类我们真正可以实现什么 让我们先定义序列化 然后我们才能讨论它为什么如此有用 序列化只是将现有对象转换为字节数组 该
  • Java时区混乱

    我正在运行 Tomcat 应用程序 并且需要显示一些时间值 不幸的是 时间快到了 还有一个小时的休息时间 我调查了一下 发现我的默认时区被设置为 sun util calendar ZoneInfo id GMT 08 00 offset
  • 我怎样才能限定我不“拥有”的自动装配设置器

    要点是 Spring Batch v2 测试框架具有JobLauncherTestUtils setJob与 Autowired注解 我们的测试套件有多个Job类提供者 由于这个类不是我可以修改的东西 我不确定如何限定它自动连接的作业 每个
  • 从应用程序内发送电子邮件中的图像和文本

    如何从我的应用程序内通过电子邮件发送图像和文本 表格数据形式 请大家帮忙并提出建议 谢谢 void sendMailWithImage UIImage image if MFMailComposeViewController canSend
  • iOS 中是否需要 Google App Indexing SDK 才能使用 Google DeepLinking?

    我想用谷歌应用程序索引与我的网页和 iOS 应用程序 我支持通用链接 or 深层链接用谷歌术语 与苹果Search并相应地设置我的网页 From 谷歌文档 https developers google com app indexing i

随机推荐

  • 与 Integer.MAX_VALUE 相比并使用 System.out.println 时 for 循环提前终止

    当我运行这个类时 for 循环似乎提前终止 class Test public static void main String args int result 0 int end Integer MAX VALUE int i for i
  • 如何从div中删除最后一个元素?

    我有描述列表 删除其中一个后 我想从 div 中删除最后一个元素 而不是刷新站点 事实上我不懂javascript 所以我想问一下我的destroy js erb应该是什么样子 我可以使用刷新整个班级的 描述 descriptions lo
  • Actionscript:图像到base64字符串可能吗?

    是否可以将选定的图像转换为 Base64 编码的字符串 对于图像上传器来说 这将是一个很好且简单的解决方案 谢谢 如果您想对加载图像的 byteArray 进行编码 您可以使用 mx utils 中的 Base64Encoder 类Base
  • 如何在 Selenium + Python 中查找损坏的链接

    我试图在 Selenium 和 Python 中找到损坏的链接 但在代码中出现错误 import requests from selenium import webdriver chrome driver path D drivers ch
  • 将 iPad 内容输出到外部显示器

    我听说可以将 iPad 应用程序的内容输出到外部显示器 但应用程序必须为此做好准备 并且存在严重的限制 有什么指点吗 另外 iPhone 也能做到这一点吗 是一样的吗 几乎没有表明 您需要创建一个新的 UIWindow 并将其附加到 UIS
  • 下划线方法前缀

    我一直在检查 CodeIgniter 和 CakePHP 的代码 我注意到它们类中的一些方法带有下划线前缀 或双下划线 这样做的目的是什么 如果不是以下任一情况PHP 魔法方法 是为了表示能见度缺乏适当的可见性关键字 蛋糕编码约定 由于我们
  • 更改fiddler代理服务器的用户名和密码[关闭]

    Closed 这个问题不符合堆栈溢出指南 目前不接受答案 我正在使用 Fiddler 设置代理服务器 但在规则菜单下启用 需要代理授权 时 用户名 密码始终为 1 如何更改用户名 密码 我尝试更改 oSession X AutoAuth 用
  • 如何读取3gp / AMR-NB音频格式的原始值?

    在我的 Android 应用程序中 我正在录制用户的声音 并将其保存为 3gp 编码的音频文件 我想做的就是打开它 即代表音频样本的序列 x n 以便执行一些音频信号分析 有谁知道我该怎么做 您可以使用安卓媒体编解码器解码 3gp 或其他媒
  • Android 中的 Button 类膨胀错误

    我有一个最小 sdk 16 到 23 的应用程序 我想尽可能多地使用 Material design 它还必须是全屏应用程序 包含 AppCompat 支持库 现在我有一些按钮的登录活动
  • 如何自动更新 Windows Mobile 应用程序

    我有一个 net cf 3 5 Windows Mobile 应用程序 我的客户希望它具有自动更新功能 这是我到目前为止所拥有的 使用智能设备 CAB 项目创建 CAB 这是否足够好 或者我应该在这里做其他事情 2 获取应用程序版本号 As
  • iOS - 关闭作为 inputView 呈现的 UIDatePicker

    我的 UI 中有一个文本字段 当选择它时会显示 UIDatePicker 而不是默认键盘 我如何设置一个按钮以便在用户完成后关闭选择器 我所做的是将我的 inputView 作为自定义视图 其中包含UIDatePicker其上方有一个工具栏
  • $this->load->model() 在 CodeIgniter 中不起作用

    我正在使用 CodeIgniter 2 1 2 这是我现在的情况 我有一个名为 math php 的模型C wamp www cr8v application models 我正在尝试将其加载到我的控制器中C wamp www cr8v a
  • Ajax文件下载问题

    我正在我的应用程序中下载动态文件 使用 iframe 模拟 ajax 我正在做的是 当发出下载请求时 我将创建一个动态的不可见 iframe 并将 iframe 的 src 设置为下载网址 我能够成功下载文件 但要求是显示下载一旦下载开始
  • jQuery 1.10.1 在选择上设置不存在的值

    有人可以解释一下这种行为
  • Java内存模型中的Happens-Before关系

    关于 JLS ch17线程和锁 它表示 如果一个操作发生在另一个操作之前 则第一个操作对第二个操作可见并且在第二个操作之前排序 我想知道 1 之前订购 到底是什么意思 因为即使action a发生在action b之前 在某些实现中acti
  • Bootstrap 3+Rails 4 - 某些 Glyphicons 不工作

    我正在尝试在我的 Rails 4 应用程序中使用 Bootstrap 3 已关注this使用 bootstrap saas 设置 bootstrap 3 的教程thisgithub 页面 Bootstrap 工作正常 但字形图标未按预期工作
  • C# 如何正确地对遵循装饰器模式的类进行单元测试?

    我对单元测试相当陌生 我们说话时我实际上正在研究它 我的目标当然是能够在下面的类中测试该方法 该类只是检查输入是否已经在缓存中 如果输入不在缓存中 它将返回输入的反转形式 虽然实现不在这里 但假设它存在 因为目的只是为了测试 基本上 目标是
  • 获取与 int 值关联的枚举

    以前 我将 LegNo 枚举简单定义为 NO LEG LEG ONE LEG TWO 并通过调用return LegNo values i 我能够获得与每个枚举相关的值 但现在我决定我想要LegNo enum NO LEG为 int 1 而
  • 如何传递列表元素作为引用?

    我将列表的单个元素传递给函数 我想修改该元素 从而修改列表本身 def ModList element element TWO l list l append one l append two l append three print l
  • 在 iOS/Swift 中创建并导出为 Base64 的 RSA 公钥在 Java 中无法识别

    TL DR 在 iOS 中生成并存储在钥匙串中 导出为 base64 并发送到 java 后端的 RSA 公钥无法识别 我正在 iOS 应用程序中实现聊天加密功能 并使用对称 非对称密钥来处理它 无需过多讨论细节 在后端 我使用用户的公钥来