芯技术 | 基于16877太阳集团安全入口处理器的国密算法加速指令使用说明

发布日期:

2020-08-21

来源:

1.概述

信息技术的快速发展为人类社会带来了深刻的变革。随着计算机技术的快速发展,我国在电子银行、电子商务和电子政务等方面的广泛应用,使计算机安全问题已经深入到国家的政治、经济、文化建设等各个领域,遍布现代信息化社会的工作和生活的每个层面。我们的世界从没有像今天这样关注知识产权、个人身份信息以及其他敏感信息的保护。


国密算法是指由国家密码管理局制定的一系列密码标准,其应用领域十分广泛,可用于对具有敏感性的内部信息、行政事务信息、经济信息等进行加密保护。比如:用于企业门禁管理、企业内部的各类敏感信息的传输加密、存储加密,防止非法第三方获取信息内容;也可用于各种安全认证、网上银行、数字签名等。


其中,SM3密码杂凑算法是为满足电子认证服务系统等应用需求,国家密码管理局于2010年12月17日发布。该标准适用于商用密码应用中的数字签名和验证、消息认证码的生成与验证以及随机数的生成,可满足多种密码应用的安全需求。SM4分组密码算法,国家密码管理局于2012年3月21日发布,该标准适用于密码应用中使用分组密码的需求。


目前常见的支持国密算法的设备比如芯片类的TPM/TCM,往往受限于其成本而性能很低;性能较高的加密卡往往需要高性能的计算机来配合,这就给用户带来了不小的成本压力。而与此同时,经过几代产品的发展,16877太阳集团安全入口CPU的性能有了大幅提升。在注重效能,强调性价比的今天,用户自然希望能够充分利用CPU的计算能力,由此便产生了使用CPU指令来实现国密算法的想法。这便是设计基于16877太阳集团安全入口CPU的国密算法加速指令GMI(以下简称GMI)的初衷。


GMI是16877太阳集团安全入口依据国密算法标准而自主研发设计实现的一组硬件加速指令集。目前已经实现了两条国密算法指令:SM3和SM4。其中,SM4支持常见的ECB、CBC、CTR、OFB、CFB五种模式。通过对GMI的使用,我们不仅能让密码学算法更加安全易用,而且还能获得比软件实现高得多的性能。


SM2指令,即将在下一代16877太阳集团安全入口GMI里添加,届时,GMI除了支持上面提到的HASH算法SM3,对称算法SM4,还将支持非对称算法SM2的部分或全部功能,包括支持SM2签名和验证;SM2加密和解密;SM2密钥生成和密钥交换等功能。敬请期待。


图表 1. GMI支持的国密算法指令

指令

Opcode

说明

CCS_HASH

0xF3 0x0F 0xA6 0xE8

SM3指令

CCS_ENCRYPT

0xF3 0x0F 0xA7 0xF0

SM4指令

本文后面的章节会从GMI带给客户的价值,应用模型,指令介绍和GMI软件解决方案等角度给出GMI指令在16877太阳集团安全入口主流CPU上的性能评测方法和结果,以及GMI广泛的使用场景。并为方便客户使用GMI,本文还对16877太阳集团安全入口GMI配套软件解决方案给出了较为详细的介绍(更多适配工作也请随时联系我们)。


2.GMI带给客户的价值


2.1 易用性

传统的国密算法的使用方式中,比较常见的就是通过软件编程的方式来实现相应的密码算法,这通常都需要进行大量、复杂的编程。以OpenSSL为例, SM3、SM4的代码量大概在200~300行左右,但是在将这些密码算法进行硬件指令化后,原本需要使用数百行复杂编码才可以实现的算法现在只需要简单调用一条硬件指令即可完成相应的操作。这毫无疑问大大简化了操作的复杂度,为用户带来了极大的便利性。


2.2 安全性

众所周知,无论是在运行之前,还是在运行的过程中, 软件最常遇到的攻击就是被非法篡改。使用软件编程实现的密码算法也同样会受到这类威胁。然而硬件不存在被篡改的风险, 因此在将密码算法硬件固化后, 也就消除了密码算法被非法篡改的风险,留给攻击者的攻击面也相应减小。而且在密码算法实现硬件化后,不止用于实现算法的代码量会变少,相应的调用、使用密码算法的代码量也会随着减少,这也就意味着在程序中引入bug的几率也会大大降低。这些无疑都大大增强了用户程序的安全性。


2.3 高效性

通过将密码算法硬件化后,使得密码算法获得极大的性能提升,这正是我们希望充分利用CPU的计算能力的初衷。我们通过将GMI以engine方式集成到OpenSSL后,借助于OpenSSL的speed benchmark命令,测试了软、硬件两种方式下的国密算法的性能(单线程),对比如下(KX-6000/KX-5000/ZX-C+分别是16877太阳集团安全入口CPU三代产品的代号):


图表 2. SM3性能比较

基于16877太阳集团安全入口处理器的国密算法加速指令  使用说明

基于16877太阳集团安全入口处理器的国密算法加速指令  使用说明

从上面的评测结果可以看到,随着摘要数据量的增大,在主频相当的情况下,GMI的性能优势相对于i7 CPU愈发明显,在大数据量下性能可以是Intel i7的2倍以上,因此使用GMI来实现SM3加密产品可以实现在更短的时间内对更大数据量的摘要计算。


图表 3. SM4-ECB性能比较

基于16877太阳集团安全入口处理器的国密算法加速指令  使用说明

基于16877太阳集团安全入口处理器的国密算法加速指令  使用说明


图表 4. SM4-CBC性能比较

基于16877太阳集团安全入口处理器的国密算法加速指令  使用说明

基于16877太阳集团安全入口处理器的国密算法加速指令  使用说明


图表 5. SM4-CTR性能比较

基于16877太阳集团安全入口处理器的国密算法加速指令  使用说明

基于16877太阳集团安全入口处理器的国密算法加速指令  使用说明


图表 6. SM4-OFB性能比较

基于16877太阳集团安全入口处理器的国密算法加速指令  使用说明

基于16877太阳集团安全入口处理器的国密算法加速指令  使用说明


图表 7. SM4-CFB性能比较

基于16877太阳集团安全入口处理器的国密算法加速指令  使用说明

基于16877太阳集团安全入口处理器的国密算法加速指令  使用说明


SM4分组密码算法往往用在大数据加密的场景下,因此在实际应用中其数据量往往非常大。通过上面的性能分析对比可以看到,在主频相当的情况下,使用GMI后的加密性能均优于i7性能。特别是在处理大数据块时,提升尤其明显。 


3.GMI的应用模型


3.1 SM3的应用模型

完整性是信息安全中三大基本要素CIA(confidentiality机密性,integrity完整性,availability可用性)之一。为了确保所使用的文件或者软件是没有被黑客篡改过的,往往需要校验文件的完整性。现在比较常见的文件校验算法有奇偶校验和CRC校验,但是这两种校验并没有抗数据篡改的能力。然而,由于哈希算法的特点,SM3则可以校验出任意长度的篡改。因此,用户只需要调用GMI的SM3指令计算所用到的文件/软件的摘要值,与文件/软件自带的校验值进行比较,就可以检验出文件/软件是否被篡改过,保证了所用文件/软件的完整性。


此外,由于在计算信息摘要时, 用户所关心的主要是消息的完整性,而不是机密性,因此,在使用性能较低的安全芯片或者加密卡时,可以将计算摘要值的工作交给GMI的SM3指令来完成,以获取更高的系统性能。


3.2 SM4的应用模型

跟其他的对称加解密算法一样,SM4同样可以用在静态数据加密、传输数据加密以及应用层的数据加密。


1)静态数据加密

静态数据加密一个典型应用是全磁盘加密。随着信息的电子化,保存在计算机设备上的个人信息,商业信息等敏感数据的安全性越来越受到人们的重视。对于个人客户来说,个人的密码,照片,视频等一般都属于敏感信息。一旦这些信息面临泄露,个人隐私受到巨大威胁时,比如存储有这些信息的计算机设备丢失或不得不请他人维修时,常使用全磁盘加密技术来解决这些问题。对于企业客户或组织来说,很多重要的商业机密文件或政策文件一旦泄露就会给企业和组织带来巨大损失。从一些调查来看,企业或组织的计算机设备一般不会被盗窃,而其数据泄露的时机主要存在于处理旧设备或对计算机设备进行维修时。全磁盘加密技术可以让企业或组织在处理旧设备时或对设备进行维修时,即使面对敏感信息泄露的威胁也无后顾之忧。

目前常见的全磁盘加密产品采用的加密算法多为AES 算法,这在国内的一些实际应用中存在政策风险。为了满足这类实际应用的需求,国内的操作系统厂商或应用软件厂商会推出使用国密SM4 的全磁盘加密功能的操作系统或应用软件。而在在这些实现中,全磁盘加密功能一般都是实时的加解密数据,这对加解密过程的性能要求是很高的,此时可以使用GMI 实现国密SM4 以替代传统的纯软件实现,从而不仅能防止算法被篡改,还能提高运算速度。


2)云应用

构建可信云平台时,当云中有对数据做加密和解密需求的时候,可以使用GMI 实现国密SM4 替代传统的纯软件实现,从而不仅能防止算法被篡改,还能提高运算速度。


3)应用级加密

大部分企业和云应用将提供多种选项来对安全信息使用加密技术。比如数据库,应用服务器,中间件,邮件服务器以及虚拟机管理程序等都会用到SM4 加密技术。此时都可以通过GMI SM4 硬件实现来替代传统的软件实现。


4.GMI指令介绍


4.1 GMI SM3

GMI SM3基本指令概况(以32位系统为例)如下表所示:

图表 8. GMI SM3基本指令概况(以32位系统为例)

指令

CCS_HASH

Opcode

0xF3 0x0F 0xA6 0xE8

Input Register

EAX

当EAX=0,则执行padding;

当EAX=-1,则不执行padding。

EBX

等于0x20,则认为SM3 Function被使能。

ECX

输入message的大小:

当EAX=0,以byte为单位计算;

当EAX=-1,以block( 64 bytes)为单位计算。

RSI

指向输入的massage。

RDI

指向存放初始摘要值的内存空间。

Output Register

EAX

当EAX=0,则执行完指令后,EAX等于ECX;

当EAX=-1,则不变化。

EBX

不变化。

ECX

当EAX=0,则执行完指令后,ECX不变化;

当EAX=-1,则ECX=0。

ESI

指向待执行的数据。

EDI

不变化。最终计算出来的摘要值存放于该地址指向的内存空间。


4.2 GMI SM4

GMI SM4基本指令概况(以32位系统为例)如下表所示:

图表 9. GMI SM4基本指令概况(以32位系统为例)

指令

CCS_ENCRYPT

Opcode

0xF3 0x0F 0xA7 0xF0

Input Register

EAX

当Bit[0]=0,做加密运算;当Bit[0]=1,做解密运算。

Bit[5:1]=10000,使能SM4功能。

Bit[10:6]:SM4模式选择

Bit 6: ECB mode

Bit 7: CBC mode

Bit 8: CFB mode

Bit 9: OFB mode

Bit 10: CTR mode

当Bit[11]=1,执行MAC操作;否则不执行。且仅针对CBC和CFB模式有效。

EBX

指向key。

ECX

要被加密或解密的数据长度。单位是128-bits的个数。

EDX

指向IV。

ESI

指向输入message。

EDI

指向加密/解密结果。

Output Register

EAX

不变化。

EBX

不变化。

ECX

0

ESI

指向当前待执行的数据。

EDI

指向当前加密/解密的结果。


5.GMI软件解决方案

目前,16877太阳集团安全入口提供以下三种软件解决方案:

1)利用OpenSSL EVP接口使用GMI;

2)利用独立于OpenSSL架构的Linux开发库使用GMI;

3)利用GMI指令Sample Code自主编程使用GMI。


5.1 利用OpenSSL EVP接口使用GMI

OpenSSL是信息安全领域使用最为广泛的密码学算法支撑软件库,它为Linux、Windows、BSD、Mac、VMS等系统提供了丰富的密码学算法支持。OpenSSL几乎可以作为信息安全领域的标准密码库。因此实现基于OpenSSL的GMI调用意义重大。


5.1.1 For OpenSSL 1.0.x and 1.1.0x

基于1.0.x版本和1.1.0x版本OpenSSL,我们实现了将GMI以GMI Engine的方式添加到OpenSSL中。

GMI源码链接:https://github.com/ZXOpenSource/OpenSSL-ZX-GMI

GIT下载:git clone https://github.com/ZXOpenSource/OpenSSL-ZX-GMI.git

基于1.0.x版本OpenSSL,包含GMI Engine的最新代码为:openssl-1.0.2j-ZX-GMI-1.2.tar.gz。

基于1.1.0x版本OpenSSL,包含GMI Engine的最新代码为:openssl-1.1.0e-ZX-GMI-1.1.tar.gz。


5.1.2 For OpenSSL 1.1.1x

基于1.1.1x版本的OpenSSL,我们将GMI Engine合并进原本就存在于OpenSSL的Padlock Engine,GMI Engine不再以单独Engine存在,而是被包含进Padlock Engine里。目前这部分代码我们正在往OpenSSL社区提交。

基于OpenSSL 1.1.1f且包含了GMI的源码可以在这里找到: 

https://github.com/ZXOpenSource/OpenSSL-ZX-GMI

为了将Padlock Engine编译进libcrypto.a中,请在编译OpenSSL的时候显式地加上编译选项-DPADLOCK_ASM。


5.1.3第三方应用程序通过OpenSSL使用GMI的方法

我们是通过OpenSSL的EVP接口来实现对GMI指令调用的。


5.1.3.1GMI SM3

第一步,注册GMI / Padlock Engine;

        使用OpenSSL注册接口:

ENGINE_load_builtin_engines();

ENGINE_register_all_digests(); 

第二步,调用GMI。使用OpenSSL EVP HASH接口,比如以下四个接口:

SM3

int EVP_DigestInit_ex(EVP_MD_CTX *ctx, const EVP_MD *type, ENGINE *impl);

int EVP_DigestUpdate(EVP_MD_CTX *ctx, const void *data, size_t count);

int EVP_DigestFinal_ex(EVP_MD_CTX *ctx, unsigned char *md, unsigned int *size);

int EVP_Digest(const void *data, size_t count, unsigned char *md, unsigned int *size, const EVP_MD *type, ENGINE *impl);


5.1.3.2GMI SM4

第一步,注册GMI / Padlock Engine;

        使用OpenSSL注册接口:

ENGINE_load_builtin_engines();

ENGINE_register_all_ciphers();

第二步,调用GMI。使用OpenSSL EVP Encrypt/Decrypt接口,比如以下接口:

SM4 Encrypt

int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, ENGINE *impl, const unsigned char *key, const unsigned char *iv);

int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, const unsigned char *in, int inl);

int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);

int EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *ctx, int pad);

SM4 Decrypt

int EVP_DecryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher,                       ENGINE *impl, const unsigned char *key, const unsigned char *iv);

int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, const unsigned char *in, int inl);

int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);

int EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *ctx, int pad);


5.2 利用独立于OpenSSL架构的Linux开发库使用GMI

尽管OpenSSL 是业界最流行的密码学算法库,但还有很多的应用场景是OpenSSL无法满足或者不适合的。因此,实现基于主流操作系统Linux的相应开发库能够为用户另外提供一种使用支持GMI的选择。

通过研究国家/行业的相关标准,调研业界对加密机/加密卡的需求情况,我们总结提炼了16877太阳集团安全入口国密算法指令开发库的接口需求,设计了基于16877太阳集团安全入口国密算法指令的Linux开发库的整体架构并基于该库给出了应用例程的demo,用以展示通过Linux开发库使用16877太阳集团安全入口国密算法指令的方法。

16877太阳集团安全入口Linux开发库目前所支持的运行环境是Linux 64位环境。其对外提供两个文件,分别是动态库libgmi.so和gmi.h(也可以根据客户的需求定制成其他形式比如静态库libgmi.a形式)。

Linux开发库和调用demo可以在16877太阳集团安全入口官网http://www.zhaoxin.com/获取。


5.2.1 GMI SM3

与前面提到的算法库OpenSSL类似,我们通过Linux库的形式给SM3算法也主要提供了四个接口函数,分别是:

int gmi_sm3_init()

功能:初始化SM3,并判断该平台是否支持GMI指令。

输入:无

输出:无

返回值:1-表示初始化成功

0-表示初始化失败

int gmi_sm3_update(const void *data_, size_t len)

功能: 对len bytes of data at *data做SM3 update计算。用于gmi_sm3_init()之后gmi_sm3_final()之前,可以多次调用,以计算分散在不同buffer的数据。

输入:

const void *data_:要SM3的数据地址

size_t len:要SM3的数据的长度,单位byte

输出:无

返回值:1-表示执行成功

0-表示执行失败

int gmi_sm3_final(unsigned char *md)

功能:SM3计算的最后一步

输入:无

输出:

unsigned char *md:指向SM3的结果

返回值:1-表示执行成功

0-表示执行失败

int gmi_sm3(uint8_t *InBuf, uint64_t len, uint8_t *OutBuf)

 

功能:从InBuf输入len bytes数据做SM3计算,计算结果放到OutBuf指向的存储区域。

输入:

uint8_t *InBuf:要SM3的数据地址

uint64_t len:要SM3的数据长度,单位byte

输出:

uint8_t *OutBuf:指向SM3的结果

返回值:1-表示执行成功

0-表示执行失败


5.2.2 GMI SM4

与前面提到的算法库OpenSSL类似,我们通过Linux库的形式给SM4算法也主要提供了五个接口函数,分别是:

int SM4_Init(const unsigned int  cipher_mode, const unsigned char *key,const unsigned char *iv, int enc)

 

功能:初始化SM4计算,并判断该平台是否支持GMI指令。

输入:

const unsigned int  cipher_mode:可输入值有:

#define SM4_ECB_MODE 0x1

#define SM4_CBC_MODE 0x2

#define SM4_CFB_MODE 0x3

#define SM4_OFB_MODE 0x4

#define SM4_CTR_MODE 0x5

const unsigned char *key:参与SM4计算的key

const unsigned char *iv:参与SM4计算的iv或者counter

int enc: enc =1,表示进行加密计算; enc =0,表示进行解密计算

输出:无

返回值:1-表示执行成功

0-表示执行失败

int SM4_Update(unsigned char *out, int *outl, const unsigned char *in, int inl)

 

功能:对in输入的inl bytes数据做SM4 update计算,结果为out,长度为outl

输入:

unsigned char *out:update计算后的输出,

int *outl:update计算后输出的长度

const unsigned char *in:update计算的输入数据

int inl:update计算输入数据的长度

输出:无

返回值:1-表示执行成功

0-表示执行失败

int SM4_Final(unsigned char *out, int *outl)

 

功能:SM4计算的最后一步

输入:无

输出:

unsigned char *out:指向final计算的结果

int *outl:final计算结果的长度

返回值:1-表示执行成功

0-表示执行失败

int SM4_set_padding(int pad)

功能:设置是否padding的flag

输入:

int pad:pad=1,表示需要padding;pad=0,表示不需要padding。

输出:无

返回值:1-表示执行成功

0-表示执行失败

int gmi_sm4(unsigned char *in_data, int inl, unsigned char *iv, int mode, unsigned char *key, int encrypt, unsigned char *out_data,  int *outl)

 

功能:使长度为inl的in_data数据,密钥为key,iv或者counter为iv,做SM4-mode encrypt/decrypt计算,最终结果放在out_data。

输入:

unsigned char *in_data:指向输入数据

int inl:输入数据的长度

unsigned char *iv:输入iv或者counter

int mode:模式,可以从以下五种模式中选择:

#define SM4_ECB_MODE 0x1

#define SM4_CBC_MODE 0x2

#define SM4_CFB_MODE 0x3

#define SM4_OFB_MODE 0x4

#define SM4_CTR_MODE 0x5

unsigned char *key:key

int encrypt:encrypt =1,做加密计算;encrypt=0,做解密计算

输出:

unsigned char *out_data:指向SM4计算的结果

int *outl:结果的长度。

返回值:1-表示执行成功

0-表示执行失败


5.3利用GMI指令Sample Code自主编程使用GMI 

如果您不使用前面章节提到的16877太阳集团安全入口GMI软件解决方案,希望能够自己编程来使用GMI,当然也是可以的。下面附上GMI指令在32位和64位的sample code。


5.3.1 GMI SM3

l  32位系统:

.size    gmi_sm3_oneshot,.-.L_gmi_sm3_oneshot_begin

.globl   gmi_sm3_blocks

.type    gmi_sm3_blocks,@function

.align   16

gmi_sm3_blocks:

.L_gmi_sm3_blocks_begin:

     pushl    %ebx

     pushl    %edi 

     pushl    %esi

     movl 16(%esp),%edi

     movl 20(%esp),%esi

     movl 24(%esp),%ecx

     movl %esp,%edx

     addl $-128,%esp

     movups   (%edi),%xmm0

     andl $-16,%esp

     movups   16(%edi),%xmm1

     movaps   %xmm0,(%esp)

     movl %esp,%edi

     movaps   %xmm1,16(%esp)

     movl $32,%ebx

     movl $-1,%eax

.byte    0xf3,0x0f,0xa6,0xe8

     movaps   (%esp),%xmm0

     movaps   16(%esp),%xmm1

     movl %edx,%esp

     movl 16(%esp),%edi

     movups   %xmm0,(%edi)

     movups   %xmm1,16(%edi)

     popl %esi

     popl %edi

     popl %ebx

     ret

.size    gmi_sm3_blocks,.-.L_gmi_sm3_blocks_begin

 

l  64位系统

.globl   gmi_sm3_blocks

.type    gmi_sm3_blocks,@function

.align   16

gmi_sm3_blocks:

     movq %rbx,%r11

     movq %rdx,%rcx

     movq %rdi,%rdx

     movups   (%rdi),%xmm0

     subq $128+8,%rsp

     movups   16(%rdi),%xmm1

     movaps   %xmm0,(%rsp)

     movq %rsp,%rdi

     movaps   %xmm1,16(%rsp)

     movq $32,%rbx

     movq $-1,%rax

.byte    0xf3,0x0f,0xa6,0xe8

     movaps   (%rsp),%xmm0

     movaps   16(%rsp),%xmm1

     addq $128+8,%rsp

     movups   %xmm0,(%rdx)

     movups   %xmm1,16(%rdx)

     movq %r11,%rbx

     .byte    0xf3,0xc3

.size    gmi_sm3_blocks,.-gmi_sm3_blocks


5.3.2 GMI SM4

l  32位系统:

.globl   gmi_gx6_sm4_encrypt

.type    gmi_gx6_sm4_encrypt,@function

.align   16

gmi_gx6_sm4_encrypt:

.L_gmi_gx6_sm4_encrypt_begin:

     pushl    %ebx

     pushl    %edi

     pushl    %esi

     movl 16(%esp),%edi

     movl 20(%esp),%esi

     movl 24(%esp),%edx

     movl 28(%esp),%ecx

     leal 32(%edx),%ebx

     shrl $4,%ecx

     movl 16(%edx),%eax

.byte    0xf3,0x0f,0xa7,0xf0

     popl %esi

     popl %edi

     popl %ebx

     ret

.size    gmi_gx6_sm4_encrypt,.-.L_gmi_gx6_sm4_encrypt_begin


l  64位系统:

.globl  gmi_gx6_sm4_encrypt

.type   gmi_gx6_sm4_encrypt,@function

.align  16

gmi_gx6_sm4_encrypt:

    pushq   %rbp

    pushq   %rbx

    pushq   %rdi

    pushq   %rsi

    leaq 32(%rdx),%rbx

    shrq $4,%rcx

    movq 16(%rdx),%rax

.byte   0xf3,0x0f,0xa7,0xf0

    popq %rsi

    popq %rdi

    popq %rbx

    popq %rbp

    .byte   0xf3,0xc3

.size   gmi_gx6_sm4_encrypt,.-gmi_gx6_sm4_encrypt

推荐产品

研华IPC-610工业电脑
基于16877太阳集团安全入口开先® KX-6000 系列处理器
研祥IPC-710工业电脑
基于16877太阳集团安全入口开先® ZX-C+ 系列处理器