Active Recordはアプリケーションレベルの暗号化をサポートします。これは、暗号化する属性を宣言し、必要に応じて暗号化と復号をシームレスに行うしくみです。暗号化の層は、データベース層とアプリケーション層の間に置かれます。アプリケーションがアクセスするのは暗号化されていないデータですが、データベースには暗号化されたデータが保存されます。
Active Record暗号化は、アプリケーション内の機密情報を保護するために存在します。典型的な機密情報の例は、個人を識別可能な情報です。既にデータベースを暗号化していてもアプリケーションレベルで暗号化したくなる理由は何でしょうか?
機密性の高い属性を暗号化することでただちに得られる実用的なメリットの1つは、セキュリティ層が追加されることです。たとえば、攻撃者がデータベースやデータベースのスナップショット、アプリケーションログにアクセスできたとしても、暗号化された情報は読めません。また、暗号化することで、開発者がうっかりユーザーの機密情報をアプリケーションログに出力してしまうことを防止できます。
しかしもっと重要なのは、Active Record暗号化を用いることで、アプリケーション内にある機密情報の構成要素をコードレベルで定義できることです。Active Record暗号化を利用すれば、アプリケーション内のデータアクセスやデータを実際に消費するサービスを細かく制御できるようになります。暗号化されたデータを保護する監査機能付きのRailsコンソールや、コントローラのparamsを自動フィルタする組み込みシステムなども検討できます。
bin/rails db:encryption:init
を実行して、ランダムなキーセットを生成します。
$ bin/rails db:encryption:init Add this entry to the credentials of the target environment: active_record_encryption: primary_key: EGY8WhulUOXixybod7ZWwMIL68R9o5kC deterministic_key: aPA5XyALhf75NNnMzaspW7akTfZp0lPY key_derivation_salt: xEY0dt6TZcAMg52K7O84wYzkjvbA62Hz
これらの値は、生成された値を既存のRails credentialsにコピーして貼り付けることで保存できます。また、環境変数など他のソースを用いてこれらの値を設定することも可能です。
config.active_record.encryption.primary_key = ENV["ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY"] config.active_record.encryption.deterministic_key = ENV["ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY"] config.active_record.encryption.key_derivation_salt = ENV["ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT"]
生成される値は32バイト長です。これらを自分で生成する場合は、最低でも主キー用に12バイト(これはAESの32バイトのキー導出に用いられます)、ソルト(salt)用に20バイトが必要です。
暗号化可能な属性はモデルレベルで定義します。これらの属性は、同名のカラムを用いる通常のActive Record属性です。
class Article < ApplicationRecord encrypts :title end
このライブラリは、属性をデータベースに保存する前に透過的に暗号化し、取得時に復号するようになります。
article = Article.create title: "すべて暗号化せよ!" article.title # => "すべて暗号化せよ!"
しかし背後で実行されるSQLは以下のようになります。
INSERT INTO `articles` (`title`) VALUES ('{\"p\":\"n7J0/ol+a7DRMeaE\",\"h\":{\"iv\":\"DXZMDWUKfp3bg/Yu\",\"at\":\"X1/YjMHbHD4talgF9dt61A==\"}}')
暗号化では容量が余分に必要です。これは、Base64エンコーディングや暗号化済みペイロードと一緒に保存されるメタデータがあるからです。 組み込みのエンベロープ暗号化キープロバイダを使う場合、最悪の場合のオーバーヘッドは約255バイトが見込まれます。このオーバーヘッドは、サイズが大きくなれば無視できるほど小さくなります。それに加えて、ライブラリがデフォルトで圧縮を行うため、大きなペイロードの場合、非暗号化バージョンと比較して最大30%のストレージ削減が可能です。
ただし、文字列のカラムサイズについては重要な懸念事項があります。 最近のデータベースでは、カラムサイズはバイト数ではなく、アロケーション可能な文字数(number of characters)で決定されます。
たとえば、UTF-8では1文字あたり4バイトまで利用可能なので、UTF-8を利用しているデータベースのカラムは、バイト数*で見ると、そのサイズの4倍まで容量が増加する可能性があります。そして、暗号化済みペイロードはBase64としてシリアライズされたバイナリ文字列なので、通常のstring
カラムに保存可能です。これらはASCIIバイトのシーケンスなので、暗号化済みカラムは平文カラムの4倍までサイズが増加する可能性があります。
すなわち、データベースに保存するバイト数が同じでも、カラムのサイズは4倍大きくなければならないことになります。
これは、実際には以下のようになります。
欧米のアルファベット(主にASCII文字)で書かれた短い文章を暗号化する場合は、カラムサイズを定義する際に255バイトのオーバーヘッド追加分を考慮しておく必要があります。
キリル文字のような非西洋アルファベットで書かれた短いテキストを暗号化する場合、カラムサイズを4倍にしておく必要があります。
長い文章を暗号化する場合、カラムサイズに関する懸念は無視できます。
以下に例を示します。
暗号化するコンテンツ | 元のカラムサイズ | 暗号化カラムの推奨サイズ | ストレージのオーバーヘッド(最大) |
---|---|---|---|
メールアドレス | string(255) | string(510) | 255 bytes |
絵文字の短いシーケンス | string(255) | string(1020) | 255 bytes |
非西洋アルファベットのサマリーテキスト | string(500) | string(2000) | 255 bytes |
任意の巨大テキスト | text | text | 無視可能 |
ActiveRecord暗号化では、デフォルトで非決定論的な(non-deterministic)暗号化を用います。ここで言う非決定論的とは、同じコンテンツを同じパスワードで暗号化しても、暗号化のたびに異なる暗号文が生成されるという意味です。非決定論的な暗号化手法によって、暗号解析の難易度を高めてデータベースへのクエリを不可能にすることで、セキュリティを向上させます。
deterministic:
オプションを指定することで、初期化ベクトルを決定論的な手法で生成できるようになり、暗号化データへのクエリを効率よく行えるようになります。
class Author < ApplicationRecord encrypts :email, deterministic: true end Author.find_by_email("some@email.com") # You can query the model normally
データをクエリする必要が生じない限り、非決定論的な手法が推奨されます。
非決定論的モードのActive Recordでは、256ビットキーとランダムな初期化ベクトルを用いるAES-GCMが使われます。決定論的モードも同様にAES-GCMを用いますが、その初期化ベクトルは、キーと暗号化対象コンテンツのHMAC-SHA-256ダイジェストとして生成されます。
deterministic_key
を省略すると、決定論的暗号化を無効にできます。
Action Textの属性宣言にencrypted: true
を渡すことで、属性を暗号化できます。
class Message < ApplicationRecord has_rich_text :content, encrypted: true end
Action Text属性に個別の暗号化オプションを渡すことについてはまだサポートされていません。グローバルな暗号化オプションに設定されている非決定的暗号化が用いられます。
config/environmentsのtest.rb
に以下のオプションを追加すると、Railsのフィクスチャが自動的に暗号化されるようになります。
config.active_record.encryption.encrypt_fixtures = true
この機能を有効にすると、暗号化済み属性はモデルで定義されている暗号化設定に沿って暗号化されるようになります。
Action Textのフィクスチャを暗号化するには、fixtures/action_text/encrypted_rich_texts.yml
に置く必要があります。
active_record.encryption
は、暗号化の前に背後の型を用いて値をシリアライズしますが、カスタムのmessage_serializer
を使わない場合は、型は文字列としてシリアライズ可能でなければなりません。serialized
などの構造化された型はそのまま利用可能です。
カスタム型をサポートする必要がある場合は、シリアライズ化属性の利用が推奨されます。シリアライズ化属性の宣言は、以下のように暗号化宣言より上の行に置いてください。
# 正しい class Article < ApplicationRecord serialize :title, type: Title encrypts :title end # 誤り class Article < ApplicationRecord encrypts :title serialize :title, type: Title end
決定論的に暗号化されたデータへのクエリで大文字小文字を区別しないようにする必要が生じることがあります。この場合、大文字小文字を区別しないクエリをやりやすくするには2とおりの方法が考えられます。
1つは、以下のように暗号化属性を宣言するときにdowncase:
オプションを指定することでコンテンツ暗号化の前に小文字に揃えておくことです。
class Person encrypts :email_address, deterministic: true, downcase: true end
downcase:
オプションを指定する場合は、元の大文字小文字の区別が失われます。大文字小文字の区別を失わずに、クエリでのみ大文字小文字を区別しないようにしたいこともあるでしょう。そのような場合は:ignore_case
オプションを指定できます。このオプションを利用する場合は、大文字小文字の区別を維持したコンテンツを保存するためのoriginal_<カラム名>
というカラムを追加する必要があります。
class Label encrypts :name, deterministic: true, ignore_case: true # 大文字小文字を維持したコンテンツは`original_name`カラムに保存される end
暗号化されていないデータを移行しやすくするため、このライブラリにはconfig.active_record.encryption.support_unencrypted_data
オプションも用意されています。このオプションをtrue
にすると以下のようになります。
config.active_record.encryption.extend_queries = true
を設定する必要があります。このオプションは、非暗号化データと暗号化済みデータの共存が避けられないので、あくまで過渡期の利用が目的です。デフォルトでは上記2つのオプションはどちらもfalse
に設定されます。そしてこの設定は、あらゆるアプリケーションで推奨される目標でもあります。
属性の暗号化プロパティを変更すれば既存のデータが破損します。たとえば、決定論的な属性を非決定的に変える場合、単にモデル内の宣言を変更すると、暗号化手法が異なるため、既存の暗号文を読み取れなくなってしまいます。
このような状況をサポートするため、以前の暗号化スキームを以下の2つのシナリオで利用される形で宣言できます。
config.active_record.encryption.extend_queries = true
を設定しておかなければなりません。以前の暗号化スキームは、以下のいずれかの方法で設定可能です。
以前の暗号化スキームは、config/application.rb
で以下のようにprevious
設定を用いてプロパティのリストとして追加可能です。
config.active_record.encryption.previous = [ { key_provider: MyOldKeyProvider.new } ]
属性を宣言するときに以下のように:previous
で指定します。
class Article encrypts :title, deterministic: true, previous: { deterministic: false } end
以前の暗号化スキームを追加する場合、以下のように非決定論的/決定論的によって違いが生じます。
決定的暗号化をあえて利用する場合は、暗号文を変えたくないのが普通です。この振る舞いはdeterministic: { fixed: false }
で変更可能です。これにより、新しいデータを暗号化するときに最新の暗号化スキームが用いられるようになります。
一意性制約は、決定論的に暗号化されたデータでしか利用できません。
一意性バリデーションは、config.active_record.encryption.extend_queries = true
によって拡張クエリが有効になっている限り、通常どおりサポートされます。
class Person validates :email_address, uniqueness: true encrypts :email_address, deterministic: true, downcase: true end
これは、暗号化済みデータと非暗号化データを組み合わせた場合や、以前の暗号化スキームを設定した場合にも利用できます。
大文字小文字を区別しないようにしたい場合は、encrypts
宣言でdowncase:
またはignore_case:
を必ず指定すること。また、バリデーション内ではcase_sensitive:
は機能しません。
決定論的に暗号化されたカラムで一意インデックスをサポートするには、その暗号文が絶対に変更されないようにしておく必要があります。
決定論的な属性ではそのために、複数の暗号化スキームが設定されている場合はデフォルトでは常に最も古い暗号化スキームを利用するようになっています。そうでないと、属性の暗号化プロパティの変更を別の方法で防止しない限り、一意インデックスが効かなくなってしまいます。
class Person encrypts :email_address, deterministic: true end
デフォルトでは、暗号化済みカラムはRailsのログで自動的にフィルタされます。config/application.rb
に以下を追加することで、この振る舞いを無効にできます。
config.active_record.encryption.add_to_filter_parameters = false
フィルタを有効にした状態で、特定のカラムをフィルタから除外したい場合は、config.active_record.encryption.excluded_from_filter_parameters
に以下を追加します。
config.active_record.encryption.excluded_from_filter_parameters = [:catchphrase]
フィルタパラメータを生成するとき、Railsはモデル名をプレフィックスとして使います。たとえば、Person#name
の場合、フィルタパラメータはperson.name
になります。
このライブラリは、非決定論的に暗号化された文字列値のエンコードを維持します。
エンコーディングは、暗号化済みペイロードとともに保存されるので、デフォルトでは強制的にUTF-8エンコーディングが使われます。すなわち、値が同じでもエンコードが異なれば暗号文も異なるものになります。これは、クエリや一意性制約を機能させるうえでは避けたいものなので、ライブラリが代わりに自動的に変換します。
決定論的暗号化でデフォルトのエンコードを指定するには、以下の設定を使います。
config.active_record.encryption.forced_encoding_for_deterministic_encryption = Encoding::US_ASCII
この振る舞いを無効にして常にエンコードを維持するには、以下の設定を使います。
config.active_record.encryption.forced_encoding_for_deterministic_encryption = nil
このライブラリは、暗号化済みペイロードをデフォルトで圧縮します。これにより、ペイロードが大きい場合にストレージスペースを最大30%節約できます。暗号化する属性にcompress: false
を設定することで、圧縮を無効できます。
class Article < ApplicationRecord encrypts :content, compress: false end
圧縮に使うアルゴリズムをカスタム設定することも可能です。デフォルトの圧縮プログラムはZlib
です。#deflate(data)
と#inflate(data)
に応答するクラスまたはモジュールを作成すれば、独自の圧縮プログラムを実装できます。
require "zstd-ruby" module ZstdCompressor def self.deflate(data) Zstd.compress(data) end def self.inflate(data) Zstd.decompress(data) end end class User encrypts :name, compressor: ZstdCompressor end
以下のように、圧縮プログラムをグローバルに設定することも可能です。
config.active_record.encryption.compressor = ZstdCompressor
キープロバイダは、キー管理戦略を実装します。キープロバイダはグローバルに設定することも、属性ごとに指定することも可能です。
DerivedSecretKeyProvider
DerivedSecretKeyProvider
は、指定のパスワードからPBKDF2を用いて導出されるキーを提供するキープロバイダです。
config.active_record.encryption.key_provider = ActiveRecord::Encryption::DerivedSecretKeyProvider.new(["some passwords", "to derive keys from. ", "These should be in", "credentials"])
active_record.encryption
はデフォルトで、active_record.encryption.primary_key
で定義されているキーを用いるDerivedSecretKeyProvider
を設定します。
EnvelopeEncryptionKeyProvider
EnvelopeEncryptionKeyProvider
は、シンプルなエンベロープ暗号化戦略を実装します。
active_record.encryption.primary_key
credentialで定義されている主キーを用いて暗号化される以下をconfig/application.rb
に追加することで、Active Recordでこのキープロバイダを使うよう設定できます。
config.active_record.encryption.key_provider = ActiveRecord::Encryption::EnvelopeEncryptionKeyProvider.new
他の組み込みのキープロバイダと同様に、active_record.encryption.primary_key
に主キーのリストを渡すことでキーローテーションスキームを実装できます。
より高度なキー管理スキームを利用したい場合は、イニシャライザで以下のようにカスタムのキープロバイダを設定できます。
ActiveRecord::Encryption.key_provider = MyKeyProvider.new
キープロバイダは以下のインターフェイスを実装しなければなりません。
class MyKeyProvider def encryption_key end def decryption_keys(encrypted_message) end end
2つのメソッドは、いずれもActiveRecord::Encryption::Key
オブジェクトを返します。
encryption_key
: コンテンツの暗号化に使われたキーを返すdecryption_keys
: 指定のメッセージを復号するのに使う可能性のあるキーのリストを返す1つのキーには、メッセージと一緒に暗号化なしで保存される任意のタグを含められます。ActiveRecord::Encryption::Message#headers
を使って、復号時にこれらの値を調べられます。
key_provider:
オプションで、キープロバイダを属性ごとに設定できます。
class Article < ApplicationRecord encrypts :summary, key_provider: ArticleKeyProvider.new end
key:
オプションで、指定のキーを属性ごとに設定できます。
class Article < ApplicationRecord encrypts :summary, key: "some secret key for article summaries" end
Active Recordは、データの暗号化や復号に使うキーをこのキーで導出します。
active_record.encryption
では、キーローテーションスキームの実装をサポートするキーのリストを利用できます。
active_record_encryption: primary_key: - a1cc4d7b9f420e40a337b9e68c5ecec6 # 以前のキーは引き続き既存コンテンツを復号する - bc17e7b413fd4720716a7633027f8cc4 # 新しいコンテンツを暗号化するアクティブなキー key_derivation_salt: a3226b97b3b2f8372d1fc6d497a0c0d3
これにより、「新しいキーの追加」「コンテンツの再暗号化」「古いキーの削除」を行ってキーのリストを短く保てるようになります。
キーローテーションは、決定論的暗号化では現在サポートされていません。
Active Record暗号化は、キーローテーション処理の自動管理機能をまだ提供していません(実装は可能ですが未実装の状態です)。
以下のようにactive_record.encryption.store_key_references
を設定することで、active_record.encryption
が暗号化済みメッセージそのものに暗号化キーへの参照を保存するようになります。
config.active_record.encryption.store_key_references = true
この設定を有効にすると、システムがキーのリストを探索せずにキーを直接見つけられるようになり、復号のパフォーマンスが向上します。その代わり、暗号化データのサイズがやや肥大化します。
Active Record暗号化は宣言的に利用することを念頭に置いていますが、より高度なシナリオで使えるAPIも提供しています。
article.encrypt # encrypt or re-encrypt all the encryptable attributes article.decrypt # decrypt all the encryptable attributes
article.ciphertext_for(:title)
article.encrypted_attribute?(:title)
Active Record暗号化のオプションは、config/application.rb
で行うことも(ほとんどの場合このファイルに書きます)、config/environments/<環境名>.rb
で特定の環境設定ファイルに設定することも可能です。
キーの保存場所には、Rails組み込みのcredentialサポートを用いることが推奨されます。設定プロパティを用いて手動で設定したい場合は、キーを誤ってコードと一緒にリポジトリにコミットしないようご注意ください(環境変数などを用いること)。
config.active_record.encryption.support_unencrypted_data
true
にすると、非暗号化データを通常通り読み出せるようになります。
false
にすると、非暗号化データを読み出したときにエラーになります。デフォルトはfalse
です。
config.active_record.encryption.extend_queries
true
に設定すると、決定論的に暗号化された属性を参照するクエリが、必要に応じて追加の値を含むように変更されます。追加される値は暗号化されない(config.active_record.encryption.support_unencrypted_data
がtrue
の場合)か、または以前の暗号化スキームで暗号化されます(previous:
で指定された場合)。デフォルトはfalse
です。
config.active_record.encryption.encrypt_fixtures
true
の場合、フィクスチャ内の暗号化可能な属性が読み込み時に自動的に暗号化されます。デフォルトはfalse
です。
config.active_record.encryption.store_key_references
true
にすると、暗号化キーへの参照が暗号化済みメッセージのヘッダ内に保存され、キーが複数使われている場合の暗号化が高速になります。デフォルトはfalse
です。
config.active_record.encryption.add_to_filter_parameters
true
にすると、暗号化された属性名が自動的にconfig.filter_parameters
に追加され、ログに出力されなくなります。デフォルトはtrue
です。
config.active_record.encryption.excluded_from_filter_parameters
フィルタから除外するparamsのリストを設定します(add_to_filter_parameters
がtrue
の場合)。デフォルトは[]
です。
config.active_record.encryption.validate_column_size
カラムのサイズに応じたバリデーションを追加します。巨大な値を圧縮率の高いペイロードを用いて保存しないために推奨されている設定です。デフォルトはtrue
です。
config.active_record.encryption.primary_key
rootデータ暗号化キーの導出に用いるキーまたはキーのリストを設定します。キーの利用法はキープロバイダの設定によって異なります。active_record_encryption.primary_key
credentialで設定するのが望ましい方法です。
config.active_record.encryption.deterministic_key
決定論的暗号化で用いるキーまたはキーのリストを設定します。active_record_encryption.deterministic_key
credentialで設定するのが望ましい方法です。
config.active_record.encryption.key_derivation_salt
キー導出時に用いるソルト(salt)を設定します。active_record_encryption.key_derivation_salt
credentialで設定するのが望ましい方法です。
config.active_record.encryption.forced_encoding_for_deterministic_encryption
決定論的に暗号化された属性のデフォルトエンコーディングを設定します。このオプションをnil
にするとエンコードの強制を無効化できます。デフォルトはEncoding::UTF_8
です。
config.active_record.encryption.hash_digest_class
鍵の導出に使うダイジェストアルゴリズムです。デフォルトはOpenSSL::Digest::SHA256
です。
config.active_record.encryption.support_sha1_for_non_deterministic_encryption
ダイジェストクラスSHA1で非決定的に暗号化されたデータの復号をサポートします。デフォルトはfalse
で、config.active_record.encryption.hash_digest_class
で設定されたダイジェストアルゴリズムのみをサポートします。
config.active_record.encryption.compressor
暗号化されたペイロードを圧縮するコンプレッサーライブラリを指定します。このライブラリは、deflate
およびinflate
に応答する必要があります。デフォルトはZlib
です。コンプレッサーについて詳しくは、圧縮セクションを参照してください。
暗号化コンテキストとは、ある時点に使われる暗号化コンポーネントを定義するものです。デフォルトではグローバルな設定に基づいた暗号化コンテキストが使われますが、特定の属性で用いるカスタムコンテキストや、コードの特定のブロックを実行するときのカスタムコンテキストを定義可能です。
暗号化コンテキストの設定メカニズムは柔軟ですが高度です。ほとんどのユーザーは気にする必要はないはずです。
暗号化コンテキストに登場する主なコンポーネントは以下のとおりです。
encryptor
: データの暗号化や復号に用いる内部APIを公開します。暗号化メッセージの作成とシリアライズのためにkey_provider
とやりとりします。暗号化や復号そのものはcypher
で行われ、シリアライズはmessage_serializer
で行われます。cipher
: 暗号化アルゴリズムそのもの(AES 256 GCM)key_provider
: 暗号化と復号のキーを提供するmessage_serializer
: 暗号化されたペイロードをシリアライズおよびデシリアライズする(Message
)独自のmessage_serializer
を構築する場合は、任意のオブジェクトをデシリアライズすることのない安全なメカニズムを採用することが重要です。一般にサポートされているシナリオは、既存の非暗号化データを暗号化するときです。任意のオブジェクトがデシリアライズ可能だと、攻撃者がこれを利用して、暗号化が行われる前に改ざんしたペイロードを入力してリモートコード実行(RCE)を実行する可能性があります。つまり、独自のシリアライザではMarshal
、YAML.load
(YAML.safe_load
にすること)、JSON.load
(JSON.parse
にすること)の利用を避けるべきです。
グローバルな暗号化コンテキストはデフォルトで利用されます。他の設定プロパティと同様、config/application.rb
や環境ごとの設定ファイルで以下のように設定可能です。
config.active_record.encryption.key_provider = ActiveRecord::Encryption::EnvelopeEncryptionKeyProvider.new config.active_record.encryption.encryptor = MyEncryptor.new
以下のように属性の宣言で暗号化コンテキストを渡すことで、暗号化コンテキストを上書きできます。
class Attribute encrypts :title, encryptor: MyAttributeEncryptor.new end
ActiveRecord::Encryption.with_encryption_context
を使うと、指定のコードブロックで暗号化コンテキストを設定できます。
ActiveRecord::Encryption.with_encryption_context(encryptor: ActiveRecord::Encryption::NullEncryptor.new) do # ... end
以下を用いると、暗号化を無効にしてコードを実行できます。
ActiveRecord::Encryption.without_encryption do # ... end
この場合、暗号化テキストを読み出すと暗号文のまま返され、save
したコンテンツは暗号化なしで保存されることになります。
以下を用いると、暗号化を無効にすると同時に、暗号化済みコンテンツが上書きされないようにコードを実行できます。
ActiveRecord::Encryption.protecting_encrypted_data do # ... end
これは、暗号化データを保護しつつ、任意のコードを実行したい場合に便利です(Railsコンソールなど)。
Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。
原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨
本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。
Railsガイド運営チーム (@RailsGuidesJP)
Railsガイドは下記の協賛企業から継続的な支援を受けています。支援・協賛にご興味あれば協賛プランからお問い合わせいただけると嬉しいです。