Apache ORC 加密解析

Apache ORC 支持对列进行加密,且会对该列的统计信息一起加密。同时加密后的文件,即使 reader 没有正确的 master key 也能够正常的查看,只不过看到的都是错误的,被 mask 后的数据。

但是 ORC 的加密只能支持到列,无法精确到复杂类型的子列。如果一个 struct 列是加密列,那么它所有的子列也都会被用相同的密钥加密。

ORC 的密钥分为两个层级,分别是 LocalKey 和 MasterKey。每一个 stripe 中的每一加密列都有自己的 LocalKey。LocalKey 会用 MasterKey 加密后写在元信息里面,你需要通过一个 MasterKey 才能解开所有的 LocalKey。MasterKey 则是存储在 KeyProvider 里面,比如 KMS 这类密钥管理系统。

可以简单的认为解密的流程如下:

  1. Reader 先从 ORC 文件得知该文件对应的 MasterKey 名称,然后从 KeyProvider 拿到对应的 MasterKey。
  2. Reader 从 ORC 文件中得到一堆 EncrpytedLocalKey,这些 EncrpytedLocalKey 都是用 MasterKey 加密后存放在元信息里面的。所以拿 KeyProvider 得到的 MasterKey 对 EncrpytedLocalKey 进行解密,得到 UnencryptedLocalKey。
  3. 读取加密列的二进制数据流时,用该列对应的 UnencryptedLocalKey 进行解密,即可得到正确的数据。

Spark 创建一个 ORC 加密表 SQL 如下:

CREATE TABLE encrypted (
  ssn STRING,
  email STRING,
  name STRING
)
USING ORC
OPTIONS (
  hadoop.security.key.provider.path "kms://http@localhost:9600/kms",
  orc.key.provider "hadoop",
  orc.encrypt "pii:ssn,email",
  orc.mask "nullify:ssn;sha256:email"
)

orc.encrypt 中的 pii 是 MasterKey 的名称,表示 ssnemail 都使用 pii MasterKey 进行加解密。

orc.mask 中的 nullify 指将加密列 ssn mask为 null,sha256 指将 email 用 sha256 mask。Reader 没有正确的 MasterKey 只能读到 mask 后的值。

AES

目前 ORC 只支持 AES 对称加密。对称加密指加密解密都共用同一个密码。

Java 使用 Chiper 负责加解密。Chiper 创建需要传入一个 secret key 和一个 IV(Initialization Vector)。

IV 可以简单的认为是一个种子,其作用就是相同的明文,使用同一个 MasterKey 加密后,还能得到不同的值。

ORC 为了确保每一个 Stripe 中的每一加密列的 IV 都不同,规定如下 IV 生成规则:

  • bytes 0 to 2 – column id。
  • bytes 3 to 4 – stream kind。
  • bytes 5 to 7 – stripe id,对于在 footer 中的 encrypted 数据,stirpe id = 该文件的 stripe 数量。
  • bytes 8 to 15 – cipher block counter,这里我看 ORC 貌似偷懒了,cipher block counter 都是设置为 0 了。

KeyProvider

KeyProvider 是一个管理加解密钥的 manager。一个 KeyProvider 里面可以管理多个 key,每一个 Key 都有一个 MasterKey。在 KeyProvider 中,key 含有 name 和 version 两个属性,只有当 name 和 version 相等时,两个 key 才算相等。

KeyProvider 提供了两个核心方法:

LocalKey createLocalKey(HadoopShims.KeyMetadata key):使用该 key 生成对应的 LocalKey。返回的 LocalKey 包含 Algorithm,DecryptedKey 和 EncryptedKey 三个信息。DecryptedKey 用于去加密 ORC 的列,EncryptedKey 则写在 ORC 的 metadata 里面。EncryptedKey 需要通过该 key 的 MasterKey 解密后才能得到 DecryptedKey。

Key decryptLocalKey(HadoopShims.KeyMetadata key, byte[] encryptedKey),顾名思义,通过 MasterKey 解密 LocalKey 的 EncryptedKey,得到 DecryptedKey。

加密数据存储排布

在 Stripe 中,存在加密列的排布顺序是:

[ROW INDEX] [ENCRYPTED ROW INDEX] [DATA] [ENCRYPTED DATA] [Stripe Footer]

就是无加密的数据在前,加密后的数据在后。

在 Tail 中,加密数据排布顺序如下:

[Encrypted Stripe Statistics] [Stripe Statistics] [Footer] [PostScript] [1 byte PostScript]

未加密的 Stripe Statistics 在 ORC 也叫 metadata。

Reader 读取流程

下面对着 ORC 的 proto 文件说,最新 proto 贴下面了:

https://gist.github.com/Smith-Cruise/d016ab9d7c9fa2e4a6e4dc0ed569b1a7

PostScript

先读 PostScript,里面 stripe_statistics_length 即 encrypted stripe statistics 的长度。

光知道 length 没用,还得知道 offset。

Encrypted stripe statistics 的 offset 计算公式如下:

offset = file_length - 1 bytes - postscript_length - footer_length - metadata_length - stripe_statistics_length

metadata_length 就是 unencrypted stripe statistics 的长度。

Footer

Footer 中的 optional Encryption encryption = 10; 存储着关于加密的信息。

message Encryption {
  // all of the masks used in this file
  // 其实这个对于 reader 没啥用,只是描述了一下加密列是如何 mask 的
  repeated DataMask mask = 1;
  // all of the keys used in this file
  // 数组,表示这个文件用了多少个 key,到时候从 KeyProvider 获取该 key 的 MasterKey。
  repeated EncryptionKey key = 2;
  // The encrypted variants.
  // Readers should prefer the first variant that the user has access to
  // the corresponding key. If they don't have access to any of the keys,
  // they should get the unencrypted masked data.
  // 数组,每一个加密列都有一个 EncryptionVariant
  repeated EncryptionVariant variants = 3;
  // How are the local keys encrypted?
  optional KeyProviderKind key_provider = 4;
}

EncryptionVariant 中的 Varint 让我想起来 Loki 里面的 variant,变体。每一个加密列都是一个 EncryptionVariant

// The description of an encryption variant.
// Each variant is a single subtype that is encrypted with a single key.
message EncryptionVariant {
  // the column id of the root
  // 对应的列 id,如果是复杂类型,则包含其所有子列
  optional uint32 root = 1;
  // The master key that was used to encrypt the local key, referenced as
  // an index into the Encryption.key list.
  // key 索引,对应上面 Encryption 中 EncryptionKey 的数组下标,意思是该 EncryptionVariant 用了哪一个 EncryptionKey
  optional uint32 key = 2;
  // the encrypted key for the file footer
  // 用于解密在文件末尾的 encrypted stripe statistics 和 encrypted file statistics 的 EncryptedLocalKey
  optional bytes encrypted_key = 3;
  // the stripe statistics for this variant
  repeated Stream stripe_statistics = 4;
  // encrypted file statistics as a FileStatistics
  optional bytes file_statistics = 5;
}

stripe_statistics 二进制数据在文件末尾的 Encrypted Stripe Statistics 中。其 Stream 的 Kind 为 STRIPE_STATISTICS。二进制数据反序列化为 ColumnarStripeStatistics 结构。

file_statistics 反序列化成 FileStatistics 结构。

EncryptionVariant 中的 encrypted_key 通过 KeyProvider 解密后,得到 DecryptedKey,然后用 DecryptedKey 去解密 stripe_statisticsfile_statistics。注意这里的 DecryptedKey 只能解文件末尾的加密数据。

ColumnarStripeStatisticsFileStatistics 只包含该 EncryptionVariant 所对应加密列的统计信息。

Stripe

Footer 中的 StripeInformation 包含了每一个 Stripe 每一列的 LocalKey。

message StripeInformation {
  // the global file offset of the start of the stripe
  optional uint64 offset = 1;
  // the number of bytes of index
  optional uint64 index_length = 2;
  // the number of bytes of data
  optional uint64 data_length = 3;
  // the number of bytes in the stripe footer
  optional uint64 footer_length = 4;
  // the number of rows in this stripe
  optional uint64 number_of_rows = 5;
  // If this is present, the reader should use this value for the encryption
  // stripe id for setting the encryption IV. Otherwise, the reader should
  // use one larger than the previous stripe's encryptStripeId.
  // For unmerged ORC files, the first stripe will use 1 and the rest of the
  // stripes won't have it set. For merged files, the stripe information
  // will be copied from their original files and thus the first stripe of
  // each of the input files will reset it to 1.
  // Note that 1 was choosen, because protobuf v3 doesn't serialize
  // primitive types that are the default (eg. 0).
  optional uint64 encrypt_stripe_id = 6;
  // For each encryption variant, the new encrypted local key to use
  // until we find a replacement.
  // 数组,每一个加密列都有一个 local key
  repeated bytes encrypted_local_keys = 7;
}

encrypted_local_keys 是一个数组,对应的是每一个加密列的 EncryptedLocalKey,其和 EncryptionVariant 的数组下标一一对应。

encrypt_stripe_id 则是用于生产解密 IV 的 stripe id。

EncryptedLocalKey 通过 MasterKey 解密后,得到 UnencryptedLocalKey,然后该列的 ROW INDEX 和 DATA 均用此 UnencryptedLocalKey 解密后就能得到正确数据。

总结

随手写的,如有错误,可以直接指出。

原创文章,作者:Smith,如若转载,请注明出处:https://www.inlighting.org/archives/apache-orc-encryption

打赏 微信扫一扫 微信扫一扫
SmithSmith
上一篇 2024年6月8日 下午11:54
下一篇 2024年8月10日 下午8:05

相关推荐

  • ORC vs Parquet,孰强孰弱?

    本文深入探讨了 ORC 和 Parquet 这两种主流数据湖文件格式的异同。从文件结构、类型系统、NULL处理到复杂类型存储,文章全面比较了两种格式的设计理念和实现细节。特别关注了统计信息的存储、随机 IO 性能、Footer 大小以及 Schema Evolution 支持等关键方面。虽然Parquet 在当前市场占据优势,但文章指出两种格式各有千秋,选择应基于具体使用场景。通过这篇深度分析,读者可以更好地理解这两种格式的优缺点,为数据湖存储方案的选择提供有价值的参考。

    2024年8月10日
    5162
  • 解决 IDEA 阅读 Hadoop 源码报错问题

    最近阅读 Hadoop 源码,使用 IDEA 打开 Hadoop,正常导入 maven 依赖后,发现某些类里面总是会报各种类不存在的错误,如下图: 一开始以为是因为我配置了国内 m…

    2020年12月28日
    1.7K1
  • Hadoop 完全分布式(Fully Distributed)安装

    本篇文章主要介绍如何搭建完全分布式的 Hadoop 集群,介于 Hadoop 配置复杂,特此写下此篇文章记录。 基础准备 这一次我使用三台服务器组建一个 Hadoop 集群,三台机…

    2019年10月6日
    1.1K0

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注