summaryrefslogtreecommitdiff
path: root/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/BackupStreamEncrypter.java
blob: 45798d32885a1fc3eb89b433fedc6716047a7cb9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.backup.encryption.tasks;

import android.util.Slog;

import com.android.server.backup.encryption.chunk.ChunkHash;
import com.android.server.backup.encryption.chunking.ChunkEncryptor;
import com.android.server.backup.encryption.chunking.ChunkHasher;
import com.android.server.backup.encryption.chunking.EncryptedChunk;
import com.android.server.backup.encryption.chunking.cdc.ContentDefinedChunker;
import com.android.server.backup.encryption.chunking.cdc.FingerprintMixer;
import com.android.server.backup.encryption.chunking.cdc.IsChunkBreakpoint;
import com.android.server.backup.encryption.chunking.cdc.RabinFingerprint64;

import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.crypto.SecretKey;

/**
 * Splits backup data into variable-sized chunks using content-defined chunking, then encrypts the
 * chunks. Given a hash of the SHA-256s of existing chunks, performs an incremental backup (i.e.,
 * only encrypts new chunks).
 */
public class BackupStreamEncrypter implements BackupEncrypter {
    private static final String TAG = "BackupStreamEncryptor";

    private final InputStream mData;
    private final int mMinChunkSizeBytes;
    private final int mMaxChunkSizeBytes;
    private final int mAverageChunkSizeBytes;

    /**
     * A new instance over the given distribution of chunk sizes.
     *
     * @param data The data to be backed up.
     * @param minChunkSizeBytes The minimum chunk size. No chunk will be smaller than this.
     * @param maxChunkSizeBytes The maximum chunk size. No chunk will be larger than this.
     * @param averageChunkSizeBytes The average chunk size. The mean size of chunks will be roughly
     *     this (with a few tens of bytes of overhead for the initialization vector and message
     *     authentication code).
     */
    public BackupStreamEncrypter(
            InputStream data,
            int minChunkSizeBytes,
            int maxChunkSizeBytes,
            int averageChunkSizeBytes) {
        this.mData = data;
        this.mMinChunkSizeBytes = minChunkSizeBytes;
        this.mMaxChunkSizeBytes = maxChunkSizeBytes;
        this.mAverageChunkSizeBytes = averageChunkSizeBytes;
    }

    @Override
    public Result backup(
            SecretKey secretKey, byte[] fingerprintMixerSalt, Set<ChunkHash> existingChunks)
            throws IOException, GeneralSecurityException {
        MessageDigest messageDigest =
                MessageDigest.getInstance(BackupEncrypter.MESSAGE_DIGEST_ALGORITHM);
        RabinFingerprint64 rabinFingerprint64 = new RabinFingerprint64();
        FingerprintMixer fingerprintMixer = new FingerprintMixer(secretKey, fingerprintMixerSalt);
        IsChunkBreakpoint isChunkBreakpoint =
                new IsChunkBreakpoint(mAverageChunkSizeBytes - mMinChunkSizeBytes);
        ContentDefinedChunker chunker =
                new ContentDefinedChunker(
                        mMinChunkSizeBytes,
                        mMaxChunkSizeBytes,
                        rabinFingerprint64,
                        fingerprintMixer,
                        isChunkBreakpoint);
        ChunkHasher chunkHasher = new ChunkHasher(secretKey);
        ChunkEncryptor encryptor = new ChunkEncryptor(secretKey, new SecureRandom());
        Set<ChunkHash> includedChunks = new HashSet<>();
        // New chunks will be added only once to this list, even if they occur multiple times.
        List<EncryptedChunk> newChunks = new ArrayList<>();
        // All chunks (including multiple occurrences) will be added to the chunkListing.
        List<ChunkHash> chunkListing = new ArrayList<>();

        includedChunks.addAll(existingChunks);

        chunker.chunkify(
                mData,
                chunk -> {
                    messageDigest.update(chunk);
                    ChunkHash key = chunkHasher.computeHash(chunk);

                    if (!includedChunks.contains(key)) {
                        newChunks.add(encryptor.encrypt(key, chunk));
                        includedChunks.add(key);
                    }
                    chunkListing.add(key);
                });

        Slog.i(
                TAG,
                String.format(
                        "Chunks: %d total, %d unique, %d new",
                        chunkListing.size(), new HashSet<>(chunkListing).size(), newChunks.size()));
        return new Result(
                Collections.unmodifiableList(chunkListing),
                Collections.unmodifiableList(newChunks),
                messageDigest.digest());
    }
}