一、密码学简介

1.1 base64

  • Base64 是一种通过查表的编码方法,不能用于加密,即使使用自定义的编码表也不行。
  • Base64 适用于小段内容的编码,比如数字证书签名、Cookie 的内容等。
  • 由于 = 字符也可能出现在 Base64 编码中,但 = 用在 URL、Cookie 里面会造成歧义,所以,很多Base64编码后会把 = 去掉,解码时,需要加上 = 把 Base64 字符串的长度变为 4 的倍数,再进行解码。

使用 mac 自带的 base64 编码,格式:base64 [文件路径] -o [保存文件路径]

$ base64 a.png
$ base64 a.png -o a.txt

iOS 代码编写:

NSData * data = [@"A" dataUsingEncoding:NSUTF8StringEncoding];

// base64 编码
NSLog(@"%@", [data base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]); // base64 解码
NSData * newData = [[NSData alloc] initWithBase64EncodedString:@"QQ==" options:NSDataBase64DecodingIgnoreUnknownCharacters];
NSLog(@"%@", [[NSString alloc] initWithData:newData encoding:NSUTF8StringEncoding]); QQ==
A

base64 对照表

1.2 hash

hash 又称为散列函数,在 iOS 中使用到的 hash 算法有:

  • MD5
  • SHA1/256/512
  • HMAC

这些算法都具有以下特点:

  • 算法都是公开的;
  • 对相同的数据进行加密,得到的结果是相同的;
  • 对不同的数据进行加密,得到的结果是定长的。如:MD5 32 个字符;
  • 不能反算。所以常用来做信息摘要,信息“指纹”,用来做信息识别。

  1. md5

    $ md5 -s "12345678"
    MD5 ("12345678") = 25d55ad283aa400af464c76d713c07ad

    应用场景:

    用户登录应用时,注册填入的密码,经过 hash 算法加密后,传给服务器,服务器只存储 hash 值。当用户再次登录时,只需要验证登录密码的 hash 值与服务器保存的是否相同。

    上面的应用场景足够安全吗?来看下图(网址:https://www.cmd5.com/

    由于相同的数据得到的 hash 值是相同的,当记住很多的原始数据和 hash 值的对应关系之后,是可以查询出来原始数据的。注意:这里是查询而不是反算。

    要想更加安全一些,可以采取策略:“加盐”。

    加盐:在原始数据中加入一段额外的字符串,降低查询成功率。

    $ md5 -s "12345678&&%#**#7*&@&#W#^E"
    MD5 ("12345678&&%#**#7*&@&#W#^E") = 28e2829463d71bb8931eac4517436a2e

    加盐之后是否就能高枕无忧呢?显然不能,如果开发者将盐固定写死在 app,是有可能泄露的,应用的安全性也就随之降低。

    在此基础上,引申出动态“加盐”的 HMAC 算法。

  2. HMAC

    使用 KEY 进行明文加密,然后进行两次 Hash 算法。Key 是服务器随机生成的,对应的是一个账号一个 Key。

    HMAC 是一种加密策略,两次使用的 hash 算法可以 MD5,也可以是 SHa.

1.3 对称加密算法

  • DES 加密算法简单,基本不使用;
  • 3DES 执行 3 次 DES 算法,基本不使用。
  • AES(高级密码标准),iOS中的钥匙串访问就是使用 AES

算法的特点:可以反算。

  1. 加解密过程

    • 明文 -> 密钥加密 -> 密文
    • 密文 -> 密钥解密 -> 明文
  2. 加密方式

    • EBC

      电子代码本,每一个数据块进行独立加密。

      a.txt 文件原始数据:

      11111111111111
      11111111111111
      11111111111111
      11111111111111

      终端执行命令:

      $ openssl enc -des-ecb -K 616263 -nosalt -in a.txt -out b.bin
      $ xxd b.bin

      上面采用 ecb 算法,-nosalt 不需要加盐,616263 是 abc 的 ASCII 码,.bin 是二进制文件,xxd 查看二进制文件。

      a.txt 文件修改后数据:

      11111111111111
      11111111111111
      11111111111100
      11111111111111

      终端执行同样的命令后,可以看到差别,如图:

    • CBC

      密码块链,使用一个密钥和一个初始化向量对数据进行加密。

      这种加密方式可以有效的保证数据的完整性。也就是说,如果有一个数据块丢失,就会导致后面所有的数据块都无法解密。一般这种技术用于防范窃听。

      $ openssl enc -des-cbc -iv 0102030405060708 -K 616263 -nosalt -in a.txt -out b.bin
      $ xxd b.bin

      由上可以看出,EBC 加密方式修改数据后,只影响一个数据块的加密结果;CBC 则影响之后的数据块。

1.4 非对称加密算法

RSA算法
RSA - 原理、特点(加解密及签名验签)及公钥和私钥的生成

  • RSA 是非对称加密算法,算法是公开的;
  • 公钥加密数据,私钥解密数据;
  • 私钥加密数据,公钥解密数据;
  • 公钥可以有多个,私钥只要一个。

以下进行 openssl 终端演示

  1. 常用指令

    • genrsa 生成并输入一个 RSA 私钥
    • rsautl 使用 RSA 密钥进行加密、解密、签名和验证等运算
    • rsa 处理 RSA 密钥的格式转换等问题
  2. 加密过程

    • 生成 RSA 私钥

      $ openssl genrsa -out private.pem 1024
      Generating RSA private key, 1024 bit long modulus
      。。。。。。。。。。。。。。。。。。。。。++++++
      。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。++++++
      e is 65537 (0x10001)

      openssl 默认生成 512 位的私钥,上面指定的是 1024 位的。需要注意的是,愈长的私钥被破解的机率愈低,但是相对地,我们在使用加密与解密的时间也会愈长。使用者可以自行评量。

    • 查看 RSA 私钥

      $ cat private.pem
      -----BEGIN RSA PRIVATE KEY-----
      MIICXAIBAAKBgQDQNiXkM7Sw3EwCHp1TPEFvKLe5kP7MWr+N/mf4NDlgEZa08xIT
      htAI7T7I/e6rW6Ho7HAgbucCb/cx1GEx+kH0ldRCKZ7NxS4Ueo9YjqArbsRlQDx5
      w5cVj58g5E1ZuIvR600uXWYZXAK9LS65HQoWZiWtMmuy4qPW5NtgdALQLwIDAQAB
      AoGAEby7J6CcAQETXI7dGi0k6eJPHHjUq/YDIYaNtuBEDwIQk6OtY4p1iH0lfxva
      zDBHL7+Mocaw2U1Ogqk0CnzmR1drO+ubpRRI15KKYkuS918xXqlPNPszowXns7z8
      THjpjeLgWfRAcuvVs7bHPu4C/b2smXwNBptTAtpIS2/6VSECQQDvoHA8cUgL+8Nn
      ovPMhcsK7NXoZ/gnSWh3LqECK9JIvDlkQao8mzd8SpZBf5Xya9esqCUYib6gzvlG
      /RxpUpG/AkEA3nAxicmTe5K1EBUed3zH1ALxquwy/7s1tsi87brvDlNAGi2MFDXO
      AZ9R9H1mVHxqnyekCrpCvgUv6QPsoex9kQJAedzc11BA9J8vy9fKJrvv+3lge5XM
      VKZ3cw0KouEISyc2BK+EVNgXCqWf7mVlK2j+wPauDuGWSY+YpCp6tXFhXwJBAMPQ
      pntyvWdybfx7averHErSUKa0Ce1Ag/el3VO2VU4aEXs6D2+XMgQRmdcOMXA8mqwC
      /JEJCUo4TMXnU3/0LVECQHKC4ARiTNGtCf0e+HZjT8XanqHR+O6QUMZ90m4aQGRg
      VaBLs5uNFf945FyyPpvQSJFqTZ3j17Go8dRpgd5k0q0=
      -----END RSA PRIVATE KEY-----
    • 转成明文并查看

      $ openssl rsa -in private.pem -text -out private.txt
      writing RSA key
      $ cat private.txt
      Private-Key: (1024 bit)
      modulus:
      00:d0:36:25:e4:33:b4:b0:dc:4c:02:1e:9d:53:3c:
      41:6f:28:b7:b9:90:fe:cc:5a:bf:8d:fe:67:f8:34:
      39:60:11:96:b4:f3:12:13:86:d0:08:ed:3e:c8:fd:
      ee:ab:5b:a1:e8:ec:70:20:6e:e7:02:6f:f7:31:d4:
      61:31:fa:41:f4:95:d4:42:29:9e:cd:c5:2e:14:7a:
      8f:58:8e:a0:2b:6e:c4:65:40:3c:79:c3:97:15:8f:
      9f:20:e4:4d:59:b8:8b:d1:eb:4d:2e:5d:66:19:5c:
      02:bd:2d:2e:b9:1d:0a:16:66:25:ad:32:6b:b2:e2:
      a3:d6:e4:db:60:74:02:d0:2f
      publicExponent: 65537 (0x10001)
      privateExponent:
      11:bc:bb:27:a0:9c:01:01:13:5c:8e:dd:1a:2d:24:
    • 从私钥中提取公钥

      $ openssl rsa -in private.pem -pubout -out public.pem
      writing RSA key
      $ cat public.pem
      -----BEGIN PUBLIC KEY-----
      MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDQNiXkM7Sw3EwCHp1TPEFvKLe5
      kP7MWr+N/mf4NDlgEZa08xIThtAI7T7I/e6rW6Ho7HAgbucCb/cx1GEx+kH0ldRC
      KZ7NxS4Ueo9YjqArbsRlQDx5w5cVj58g5E1ZuIvR600uXWYZXAK9LS65HQoWZiWt
      Mmuy4qPW5NtgdALQLwIDAQAB
      -----END PUBLIC KEY-----
    • 通过公钥加密数据

      $ openssl rsautl -encrypt -in a.txt -inkey public.pem -pubin -out enc.txt
      $ cat enc.txt
      1��,~��~Dn�P�Q�
      ]X
      ��wa�؆���ޅ��T���sJ��z���,l쩨�l��,Y�i�����vD9^Y��)D�:�

      RSA 非对称式加解演算法因为先天的限制,无法加密过大的档案,若遇到这个问题时,OpenSSL 会输出如下的错误讯息

      RSA operation error
      140736003920840:error:0406D06E:rsa routines:RSA_padding_add_PKCS1_type_2:data too large for key size:/BuildRoot/Library/Caches/com。apple。xbs/Sources/libressl/libressl-22.50。3/libressl/crypto/rsa/rsa_pk1.c:158:
    • 通过私钥解密数据

      $ openssl rsautl -decrypt -in enc.txt -inkey private.pem -out dec.txt
      $ cat dec.txt
      11111111111111
      11111111111111
      11111111111111
      11111111111111
    • 通过私钥加密数据

      $ openssl rsautl -sign -in dec.txt -inkey private.pem -out enc2.txt
      $ cat enc2.txt
      ]n�&h`�,ܪ8ۭ��`��X�fL8。�^����G
      \��3��P����t�Xj���&�f��_7�K�[2wK7@ʼ�_�Qh�N
      �x��。
      �uɮ�i����@����ݛ���ɧ]l�
    • 通过公钥解密数据

      $ openssl rsautl -verify -in enc2.txt -inkey public.pem -pubin -out dec2.txt
      $ cat dec2.txt
      11111111111111
      11111111111111
      11111111111111
      11111111111111
  3. 加密特点

    • 加密的安全性非常高,加密解密使用不一样的密钥;
    • 运算的效率非常低,一般只加密小数据,用于数字签名。

二、数字签名

数字信息的识别码。

  1. 用户在支付宝付费 10 元

  2. 直接传递数据是不安全的,黑客可以随意将“消费 10 元”改成“消费 100 元”,支付宝无法验证用户的数据是否被篡改,考虑后,决定将“消费 10 元”的数据连同它的 hash 值一起发送给服务器,服务器端通过对数据进行 hash 运算,与携带的 hash 值进行比较,相同则没有被篡改过。

  3. 以为这样就安全了吗?黑客通过中间拦截,将“消费 10 元”及其 hash 值一并修改。

  4. 有没有方式可以让 hash 值无法被修改呢?答案是对 hash 值数据进行非对称(RSA)加密,这就是数字签名。

三、代码签名

代码签名(Code signing)是对可执行文件或脚本进行数字签名,用来确认软件在签名后未被修改或损坏的措施。和数字签名原理一样,只不过签名的数据是代码。

3.1 简单的代码签名

在 iOS 出来之前,以前的主流操作系统(Mac/Windows)软件随便从哪里下载都能运行,系统安全存在隐患,盗版软件,病毒入侵,静默安装等等。那么苹果希望解决这样的问题,要保证每一个安装到 iOS 上的 APP 都是经过苹果官方允许的,怎样保证呢?就是通过代码签名。

如果要实现验证,其实最简单的方式就是通过苹果官方生成非对称加密的一对公私钥。在 iOS 的系统中内置一个公钥,私钥由苹果后台保存,我们传 APP 到 AppStore 时,苹果后台用私钥对 App 数据进行签名,iOS 系统下载这个 APP 后,用公钥验证这个签名,若签名正确,这个 App 肯定是由苹果后台认证的,并且没有被修改过,也就达到了苹果的需求:保证安装的每一个 App 都是经过苹果官方允许的。

如果我们 iOS 设备安装 App 只从 App Store 这一个入口这件事就简单解决了,没有任何复杂的东西,一个数字签名搞定。但是实际上 iOS 安装 App 还有其他渠道:比如在开发 App 时直接真机调试的,而且苹果还开放了企业内部分发的渠道,企业证书签名的 App 也是需要顺利安装的。

苹果需要开放这些方式安装APP,这些需求就无法通过简单的代码签名来办到了。那么我们来分析一下,它有些什么需求。

  1. 安装包不需要上传到 App Store,可以直接安装到手机上;
  2. 苹果为了保证系统的安全性,又必须对安装的 App 有绝对的控制权
    • 经过苹果允许才可以安装
    • 不能被滥用,导致非开发 App 也能被安装

为了实现这些需求,iOS 签名的复杂度也就开始增加了,苹果这里给出的方案是双层签名。

3.2 iOS 的双层代码签名

iOS 的双层代码签名流程这里简单梳理一下,这也不是最终的 iOS 签名原理。iOS 的最终签名在这个基础上还要稍微加点东西,文末会讲。

首先这里有两个角色:

  1. iOS 系统
  2. Mac 系统

因为 iOS 的 App 开发环境在 Mac 系统下,所以这个依赖关系成为了苹果双层签名的基础。

  1. 在 Mac 系统中生成非对称加密算法的一对公钥/私钥(Xcode 代办了)。这里称为公钥 M、私钥 M。

  2. 苹果自己有固定的一对公私钥,跟之前 App Store 原理一样,私钥在苹果后台,公钥在每个 iOS 系统中。这里称为公钥 A,私钥 a.

  3. 把公钥 M 以及一些你开发者的信息,传到苹果后台(这个就是 CSR 文件),用苹果后台里的私钥 A 去签名公钥 M。得到一份数据包含了公钥 M 以及其签名,把这份数据称为证书。

  4. 在开发时,编译完一个 APP 后,用本地的私钥 M(今后你导出的P12) 对这个 APP 进行签名,同时把第三步得到的证书一起打包进 APP 里,安装到手机上。

  5. 在安装时,iOS 系统取得证书,通过系统内置的公钥 A,去验证证书的数字签名是否正确。

  6. 验证证书后确保了公钥 M 是苹果认证过的,再用公钥 M 去验证 APP 的签名,这里就间接验证了这个 APP 安装行为是否经过苹果官方允许。(这里只验证安装行为,不验证 APP 是否被改动,因为开发阶段 APP 内容总是不断变化的,苹果不需要管。)

有了上面的过程,已经可以保证开发者的认证,和程序的安全性了。 但是,你要知道 iOS 的程序,主要渠道是要通过 APP Store 才能分发到用户设备的,如果只有上述的过程,那岂不是只要申请了一个证书,就可以安装到所有 iOS 设备了?那么为了防止滥用,苹果再加了几个限制。

四、描述文件

应用签名方式并不能解决应用滥用的问题,所以苹果又加了两个限制:

  1. 在苹果后台注册过的设备才可以安装
  2. 签名只能针对某一个具体的 App

并且苹果还想控制 App 里面的 iCloud/PUSH/后台运行/调试器附加这些权限,所以苹果把这些权限开关统一称为 Entitlements(授权文件)。并将这个文件放在了一个叫做 Provisioning Profile(描述文件)文件中。描述文件是在 AppleDevelop 网站创建的(在 Xcode 中填上 AppleID 它会代办创建),Xcode 运行时会打包进入 APP 内。

所以我们使用 CSR 申请证书时,我们还要申请描述文件。流程如下:

这个描述文件里面就是描述:

  1. 可以安装的设备有哪些
  2. APP 的 ID 是什么
  3. 权限有哪些

在开发时,编译完一个 APP 后,用本地的私钥 M 对这个 APP 进行签名,同时把从苹果服务器得到的 Provisioning Profile 文件打包进 APP 里,文件名为 embedded.mobileprovision,把 APP 安装到手机上。

我们可以利用 $security cms -D -i embedded.mobileprovision 命令查看 Provisioning profile 内容,这些 Xcode 创建的 Profile 文件都存放在 ~/Library/MobileDevice/Provisioning Profiles/ 目录下

注意:每次我们新建项目其实会生成一个描述文件,选择运行到手机上,我们只需要编译一下,在 App 包里面就可以看到。

那么为了便于我们查看信息! 我们可以通过 Xcode 来查看!!

当然,Provisioning profile 本身也是通过签名认证的,所以别想着你可以更改里面的东西来达到扩充权限或者设备的目的。只有老老实实的去网站向 Apple 申请一份更多权限、设备的 profile。

五、整体的流程

首先总结一下刚才的一些名词

  • 证书:内容是公钥或者私钥,由认证机构对其签名组成的数据包。可以使用钥匙串访问看到

  • P12:就是本地私钥,可以导入到其他电脑

  • Entitlements:权限文件,包含了 App 一些权限的 plist 文件

  • CertificateSigningRequest:CSR 文件包含了本地公钥的数据文件

  • Provisioning Profile:描述文件,包含了证书、Entitlements 等数据,并由苹果后台私钥签名的数据包。

流程如下:

  1. keychain 里的 “从证书颁发机构请求证书”,这里就本地生成了一对公私钥,保存的 CertificateSigningRequest 里面就包含公钥,私钥保存在本地电脑里。
  2. 向苹果申请对应把 CSR 传到苹果后台生成证书。
  3. 把证书下载到本地。这时本地有两个证书。一个是第 1 步生成的私钥,一个是这里下载回来的证书,keychain 会把这两个证书关联起来,因为他们公私钥是对应的,在 XCode 选择下载回来的证书时,实际上会找到 keychain 里对应的私钥去签名。这里私钥只有生成它的这台 Mac 有,如果别的 Mac 也要编译签名这个 App 怎么办?答案是把私钥导出给其他 Mac 用,在 keychain 里导出私钥,就会存成 .p12 文件,其他 Mac 打开后就导入了这个私钥。

  4. 在苹果网站上操作,配置 AppID/权限/设备等,最后下载 Provisioning Profile 文件。

  5. XCode 会通过第 3 步下载回来的证书(存着公钥),在本地找到对应的私钥(第一步生成的),用本地私钥去签名 App,并把 Provisioning Profile 文件命名为 embedded.mobileprovision 一起打包进去。所以任何本地调试的 App,都会有一个 embedded.mobileprovision(描述文件)从 App Store 下载的没有。

六、APP签名的数据

这里对 App 的签名数据保存分两部分

  1. Mach-O 可执行文件会把签名直接写入文件里

  2. 其他资源文件则会保存在 _CodeSignature 目录下在 App 包里。

文章

逻辑教育视频:
逻辑教育视频:双向验证
请叫我Hank - iOS应用签名(上)iOS应用签名(下)

iOS 应用签名的更多相关文章

  1. iOS代码签名理解

    前言 做了几年iOS app coder了,对于证书的生成.使用流程烂熟于心,然而对于这套机制的原理却一直不甚理解.近来由于工作需要仔细研究了一下,特将自己的学习经验记录于此,以供大家学习指正. 问题 ...

  2. iOS App签名的原理

    前言 相信很多同学对于iOS的真机调试,App的打包发布等过程中的各种证书.Provisioning Profile. CertificateSigningRequest.p12的概念是模糊的,导致在 ...

  3. 【腾讯Bugly干货分享】iOS App 签名的原理

    本文来自 WeRead 团队博客: http://wereadteam.github.io/ iOS 签名机制挺复杂,各种证书,Provisioning Profile,entitlements,Ce ...

  4. iOS app签名原理

    基本原理: 公钥能够验证私钥的签名是否正确. Apple后台有一个私钥A,iOS内置一个公钥A,与私钥A对应.(A:代表Apple,即苹果) 本地产生一对公钥L.私钥L,(L:代表Local,即本地) ...

  5. iOS 应用签名原理&重签名

    在苹果的日常开发中,真机测试与打包等很多流程都会牵扯到各种证书,CertificateSigningRequest,p12等.但是很多相应的开发者并不理解iOS App应用签名的原理和流程.今天着重讲 ...

  6. iOS 超级签名详解

    一.原理 把安装设备当做开发设备进行分发.说的明白一些,开发者可以在开发者后台添加手机的UDID,然后重新打包一个IPA文件,分发平台,然后被添加的UDID就可以下载. 二.优缺点 优势: 直接分发, ...

  7. ios企业签名为什么会掉签?

      我们都知道ios用户无法直接安装App Store之外的应用,对于那些无法上架苹果应用商店的APP,开发者们一般会选择苹果签名的形式.   目前的苹果签名有ios企业签名.超级签名和TF上架这三种 ...

  8. iOS的签名机制

    1.从keychain里“从这证书颁发机构请求证书”,这样就在本地生成了一对公私钥,保存的CertificateSigningRequest就是公钥,私钥保存在本地电脑里. 2.苹果自己有一对固定的公 ...

  9. iOS自动签名网站

    node.js作为服务端,调用shell脚本进行iOS包重签名. 需要安装:nodejs ,forever 安装环境: 安装nodejs 安装forever: npm install forever ...

随机推荐

  1. CVE-2019-0708 远程桌面漏洞复现

    漏洞影响Windows版本: Windows XP SP3 x86Windows XP Professional x64 Edition SP2Windows XP Embedded SP3 x86W ...

  2. 如何优雅地删除 Linux 中的垃圾文件

    不知道大家是否也跟我一样,是一只要把的自己电脑文件安排的条理有序,把没用的文件会及时删掉的程序猿呢?如果是的话,那么我们可以愉快地探讨下文章的内容.如果不是的话,你也可以留下来凑凑热闹嘛(>-& ...

  3. 【每日一包0017】pretty-ms

    [github地址:https://github.com/ABCDdouyae...] pretty-ms 将毫秒转换为容易读取的时间:1337000000 → 15d 11h 23m 20s 普通用 ...

  4. Web 认证配置流程

    AC配置 Radius配置 Portal配置

  5. Sequence to Sequence Learning with Neural Networks论文阅读

    论文下载 作者(三位Google大佬)一开始提出DNN的缺点,DNN不能用于将序列映射到序列.此论文以机器翻译为例,核心模型是长短期记忆神经网络(LSTM),首先通过一个多层的LSTM将输入的语言序列 ...

  6. Notepad++远程连接Linux

    为方便编辑Linux上的文件,我们可以用Notepad++的NppFTP插件 工具:Notepad++.CentOS 1.通过ifconfig命令找到主机ip 2.打开Notepad++插件NppFT ...

  7. plantUML最佳实践

    plantUML 使用plantUML中的活动图用来画流程图很好用; 但类图等就不很好用; 个人体会如下: • 时序图 推荐 • 用例图 一般 • 类 图 不推荐, 用Visual Paradigm或 ...

  8. JsonFormat 日期少了8个小时?还我

    JsonFormat 后日期少了8个小时什么鬼? 前言 今天测试的时候发现时间对不上,比数据库里的时间少了8个小时?测试小姐姐一顿狂轰乱炸,一点都不温柔. 什么鬼?哪里出了问题?数据库显示的是下面

  9. 建议8:恰当选用if和switch

    相对来说下面几种情况更适合switch结构 枚举表达式的值.这种枚举是可以期望的,平行逻辑关系的 表达式的值具有离散性,不具有线性的非连续的区间值 表达式的值是固定的,不是动态变化的 表达式的值是有限 ...

  10. rimraf node_modules 突然不能用了 怀疑是yarn的问题,从环境变量将yarn删掉,能用了

    rimraf node_modules 突然不能用了 怀疑是yarn的问题,从环境变量将yarn删掉,能用了