github.com/apache/arrow/go/v14@v14.0.2/parquet/internal/encryption/encryptor.go (about) 1 // Licensed to the Apache Software Foundation (ASF) under one 2 // or more contributor license agreements. See the NOTICE file 3 // distributed with this work for additional information 4 // regarding copyright ownership. The ASF licenses this file 5 // to you under the Apache License, Version 2.0 (the 6 // "License"); you may not use this file except in compliance 7 // with the License. You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, 13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 // See the License for the specific language governing permissions and 15 // limitations under the License. 16 17 package encryption 18 19 import ( 20 "io" 21 22 "github.com/apache/arrow/go/v14/arrow/memory" 23 "github.com/apache/arrow/go/v14/parquet" 24 ) 25 26 // FileEncryptor is the interface for constructing encryptors for the different 27 // sections of a parquet file. 28 type FileEncryptor interface { 29 // GetFooterEncryptor returns an encryptor for the footer metadata 30 GetFooterEncryptor() Encryptor 31 // GetFooterSigningEncryptor returns an encryptor for creating the signature 32 // for the footer as opposed to encrypting the footer bytes directly. 33 GetFooterSigningEncryptor() Encryptor 34 // GetColumnMetaEncryptor returns an encryptor for the metadata only of the requested 35 // column path string. 36 GetColumnMetaEncryptor(columnPath string) Encryptor 37 // GetColumnDataEncryptor returns an encryptor for the column data ONLY of 38 // the requested column path string. 39 GetColumnDataEncryptor(columnPath string) Encryptor 40 // WipeOutEncryptionKeys deletes the keys that were used for encryption, 41 // called after every successfully encrypted file to ensure against accidental 42 // key re-use. 43 WipeOutEncryptionKeys() 44 } 45 46 type fileEncryptor struct { 47 props *parquet.FileEncryptionProperties 48 columnDataMap map[string]Encryptor 49 columnMetaDataMap map[string]Encryptor 50 footerSigningEncryptor Encryptor 51 footerEncryptor Encryptor 52 53 // Key must be 16, 24, or 32 bytes in length thus there could be up to 54 // three types of meta_encryptors and data_encryptors 55 metaEncryptor *aesEncryptor 56 dataEncryptor *aesEncryptor 57 58 mem memory.Allocator 59 } 60 61 // NewFileEncryptor returns a new encryptor using the given encryption properties. 62 // 63 // Panics if the properties passed have already been used to construct an encryptor 64 // ie: props.IsUtilized returns true. If mem is nil, will default to memory.DefaultAllocator 65 func NewFileEncryptor(props *parquet.FileEncryptionProperties, mem memory.Allocator) FileEncryptor { 66 if props.IsUtilized() { 67 panic("re-using encryption properties for another file") 68 } 69 70 props.SetUtilized() 71 if mem == nil { 72 mem = memory.DefaultAllocator 73 } 74 75 return &fileEncryptor{ 76 props: props, 77 mem: mem, 78 columnDataMap: make(map[string]Encryptor), 79 columnMetaDataMap: make(map[string]Encryptor), 80 } 81 } 82 83 func (e *fileEncryptor) WipeOutEncryptionKeys() { 84 e.props.WipeOutEncryptionKeys() 85 } 86 87 func (e *fileEncryptor) GetFooterEncryptor() Encryptor { 88 if e.footerEncryptor == nil { 89 alg := e.props.Algorithm().Algo 90 footerAad := CreateFooterAad(e.props.FileAad()) 91 footerKey := e.props.FooterKey() 92 enc := e.getMetaAesEncryptor(alg) 93 e.footerEncryptor = &encryptor{ 94 aesEncryptor: enc, 95 key: []byte(footerKey), 96 fileAad: e.props.FileAad(), 97 aad: footerAad, 98 mem: e.mem, 99 } 100 } 101 return e.footerEncryptor 102 } 103 104 func (e *fileEncryptor) GetFooterSigningEncryptor() Encryptor { 105 if e.footerSigningEncryptor == nil { 106 alg := e.props.Algorithm().Algo 107 footerAad := CreateFooterAad(e.props.FileAad()) 108 footerKey := e.props.FooterKey() 109 enc := e.getMetaAesEncryptor(alg) 110 e.footerSigningEncryptor = &encryptor{ 111 aesEncryptor: enc, 112 key: []byte(footerKey), 113 fileAad: e.props.FileAad(), 114 aad: footerAad, 115 mem: e.mem, 116 } 117 } 118 return e.footerSigningEncryptor 119 } 120 121 func (e *fileEncryptor) getMetaAesEncryptor(alg parquet.Cipher) *aesEncryptor { 122 if e.metaEncryptor == nil { 123 e.metaEncryptor = NewAesEncryptor(alg, true) 124 } 125 return e.metaEncryptor 126 } 127 128 func (e *fileEncryptor) getDataAesEncryptor(alg parquet.Cipher) *aesEncryptor { 129 if e.dataEncryptor == nil { 130 e.dataEncryptor = NewAesEncryptor(alg, false) 131 } 132 return e.dataEncryptor 133 } 134 135 func (e *fileEncryptor) GetColumnMetaEncryptor(columnPath string) Encryptor { 136 return e.getColumnEncryptor(columnPath, true) 137 } 138 139 func (e *fileEncryptor) GetColumnDataEncryptor(columnPath string) Encryptor { 140 return e.getColumnEncryptor(columnPath, false) 141 } 142 143 func (e *fileEncryptor) getColumnEncryptor(columnPath string, metadata bool) Encryptor { 144 if metadata { 145 if enc, ok := e.columnMetaDataMap[columnPath]; ok { 146 return enc 147 } 148 } else { 149 if enc, ok := e.columnDataMap[columnPath]; ok { 150 return enc 151 } 152 } 153 154 columnProp := e.props.ColumnEncryptionProperties(columnPath) 155 if columnProp == nil { 156 return nil 157 } 158 159 var key string 160 if columnProp.IsEncryptedWithFooterKey() { 161 key = e.props.FooterKey() 162 } else { 163 key = columnProp.Key() 164 } 165 166 alg := e.props.Algorithm().Algo 167 var enc *aesEncryptor 168 if metadata { 169 enc = e.getMetaAesEncryptor(alg) 170 } else { 171 enc = e.getDataAesEncryptor(alg) 172 } 173 174 fileAad := e.props.FileAad() 175 ret := &encryptor{ 176 aesEncryptor: enc, 177 key: []byte(key), 178 fileAad: fileAad, 179 aad: "", 180 mem: e.mem, 181 } 182 if metadata { 183 e.columnMetaDataMap[columnPath] = ret 184 } else { 185 e.columnDataMap[columnPath] = ret 186 } 187 return ret 188 } 189 190 // Encryptor is the basic interface for encryptors, for now there's only the single 191 // aes encryptor implementation, but having it as an interface allows easy addition 192 // manipulation of encryptor implementations in the future. 193 type Encryptor interface { 194 // FileAad returns the file level AAD bytes for this encryptor 195 FileAad() string 196 // UpdateAad sets the aad bytes for encryption to the provided string 197 UpdateAad(string) 198 // Allocator returns the allocator that was used to construct the encryptor 199 Allocator() memory.Allocator 200 // CiphertextSizeDelta returns the extra bytes that will be added to the ciphertext 201 // for a total size of len(plaintext) + CiphertextSizeDelta bytes 202 CiphertextSizeDelta() int 203 // Encrypt writes the encrypted ciphertext for src to w and returns the total 204 // number of bytes written. 205 Encrypt(w io.Writer, src []byte) int 206 // EncryptColumnMetaData returns true if the column metadata should be encrypted based on the 207 // column encryption settings and footer encryption setting. 208 EncryptColumnMetaData(encryptFooter bool, properties *parquet.ColumnEncryptionProperties) bool 209 } 210 211 type encryptor struct { 212 aesEncryptor *aesEncryptor 213 key []byte 214 fileAad string 215 aad string 216 mem memory.Allocator 217 } 218 219 func (e *encryptor) FileAad() string { return e.fileAad } 220 func (e *encryptor) UpdateAad(aad string) { e.aad = aad } 221 func (e *encryptor) Allocator() memory.Allocator { return e.mem } 222 func (e *encryptor) CiphertextSizeDelta() int { return e.aesEncryptor.CiphertextSizeDelta() } 223 224 func (e *encryptor) EncryptColumnMetaData(encryptFooter bool, properties *parquet.ColumnEncryptionProperties) bool { 225 if properties == nil || !properties.IsEncrypted() { 226 return false 227 } 228 if !encryptFooter { 229 return false 230 } 231 // if not encrypted with footer key then encrypt the metadata 232 return !properties.IsEncryptedWithFooterKey() 233 } 234 235 func (e *encryptor) Encrypt(w io.Writer, src []byte) int { 236 return e.aesEncryptor.Encrypt(w, src, e.key, []byte(e.aad)) 237 }