1/*
2 * Copyright 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.privacy.internal.rappor;
18
19import android.privacy.DifferentialPrivacyEncoder;
20
21import com.google.android.rappor.Encoder;
22
23import java.nio.ByteBuffer;
24import java.nio.charset.StandardCharsets;
25import java.security.MessageDigest;
26import java.security.NoSuchAlgorithmException;
27import java.security.SecureRandom;
28import java.util.Random;
29
30/**
31 * Differential privacy encoder by using
32 * <a href="https://research.google.com/pubs/pub42852.html">RAPPOR</a>
33 * algorithm.
34 *
35 * @hide
36 */
37public class RapporEncoder implements DifferentialPrivacyEncoder {
38
39    // Hard-coded seed and secret for insecure encoder
40    private static final byte[] INSECURE_SECRET = new byte[]{
41            (byte) 0xD7, (byte) 0x68, (byte) 0x99, (byte) 0x93,
42            (byte) 0x94, (byte) 0x13, (byte) 0x53, (byte) 0x54,
43            (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54,
44            (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54,
45            (byte) 0xD7, (byte) 0x68, (byte) 0x99, (byte) 0x93,
46            (byte) 0x94, (byte) 0x13, (byte) 0x53, (byte) 0x54,
47            (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54,
48            (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54,
49            (byte) 0xD7, (byte) 0x68, (byte) 0x99, (byte) 0x93,
50            (byte) 0x94, (byte) 0x13, (byte) 0x53, (byte) 0x54,
51            (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54,
52            (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54
53    };
54    private static final SecureRandom sSecureRandom = new SecureRandom();
55
56    private final RapporConfig mConfig;
57
58    // Rappor encoder
59    private final Encoder mEncoder;
60    // True if encoder is secure (seed is securely randomized)
61    private final boolean mIsSecure;
62
63
64    private RapporEncoder(RapporConfig config, boolean secureEncoder, byte[] userSecret) {
65        mConfig = config;
66        mIsSecure = secureEncoder;
67        final Random random;
68        if (secureEncoder) {
69            // Use SecureRandom as random generator.
70            random = sSecureRandom;
71        } else {
72            // To have deterministic result by hard coding encoder id as seed.
73            random = new Random(getInsecureSeed(config.mEncoderId));
74            userSecret = INSECURE_SECRET;
75        }
76        mEncoder = new Encoder(random, null, null,
77                userSecret, config.mEncoderId, config.mNumBits,
78                config.mProbabilityF, config.mProbabilityP, config.mProbabilityQ,
79                config.mNumCohorts, config.mNumBloomHashes);
80    }
81
82    private long getInsecureSeed(String input) {
83        try {
84            MessageDigest digest = MessageDigest.getInstance("SHA-256");
85            byte[] bytes = digest.digest(input.getBytes(StandardCharsets.UTF_8));
86            return ByteBuffer.wrap(bytes).getLong();
87        } catch (NoSuchAlgorithmException e) {
88            // Should not happen
89            throw new AssertionError("Unable generate insecure seed");
90        }
91    }
92
93    /**
94     * Create {@link RapporEncoder} with {@link RapporConfig} and user secret provided.
95     *
96     * @param config     Rappor parameters to encode input.
97     * @param userSecret Per device unique secret key.
98     * @return {@link RapporEncoder} instance.
99     */
100    public static RapporEncoder createEncoder(RapporConfig config, byte[] userSecret) {
101        return new RapporEncoder(config, true, userSecret);
102    }
103
104    /**
105     * Create <strong>insecure</strong> {@link RapporEncoder} with {@link RapporConfig} provided.
106     * Should not use it to process sensitive data.
107     *
108     * @param config Rappor parameters to encode input.
109     * @return {@link RapporEncoder} instance.
110     */
111    public static RapporEncoder createInsecureEncoderForTest(RapporConfig config) {
112        return new RapporEncoder(config, false, null);
113    }
114
115    @Override
116    public byte[] encodeString(String original) {
117        return mEncoder.encodeString(original);
118    }
119
120    @Override
121    public byte[] encodeBoolean(boolean original) {
122        return mEncoder.encodeBoolean(original);
123    }
124
125    @Override
126    public byte[] encodeBits(byte[] bits) {
127        return mEncoder.encodeBits(bits);
128    }
129
130    @Override
131    public RapporConfig getConfig() {
132        return mConfig;
133    }
134
135    @Override
136    public boolean isInsecureEncoderForTest() {
137        return !mIsSecure;
138    }
139}
140