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 
17 package android.privacy.internal.rappor;
18 
19 import android.privacy.DifferentialPrivacyEncoder;
20 
21 import com.google.android.rappor.Encoder;
22 
23 import java.nio.ByteBuffer;
24 import java.nio.charset.StandardCharsets;
25 import java.security.MessageDigest;
26 import java.security.NoSuchAlgorithmException;
27 import java.security.SecureRandom;
28 import 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  */
37 public 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 
RapporEncoder(RapporConfig config, boolean secureEncoder, byte[] userSecret)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 
getInsecureSeed(String input)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      */
createEncoder(RapporConfig config, byte[] userSecret)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      */
createInsecureEncoderForTest(RapporConfig config)111     public static RapporEncoder createInsecureEncoderForTest(RapporConfig config) {
112         return new RapporEncoder(config, false, null);
113     }
114 
115     @Override
encodeString(String original)116     public byte[] encodeString(String original) {
117         return mEncoder.encodeString(original);
118     }
119 
120     @Override
encodeBoolean(boolean original)121     public byte[] encodeBoolean(boolean original) {
122         return mEncoder.encodeBoolean(original);
123     }
124 
125     @Override
encodeBits(byte[] bits)126     public byte[] encodeBits(byte[] bits) {
127         return mEncoder.encodeBits(bits);
128     }
129 
130     @Override
getConfig()131     public RapporConfig getConfig() {
132         return mConfig;
133     }
134 
135     @Override
isInsecureEncoderForTest()136     public boolean isInsecureEncoderForTest() {
137         return !mIsSecure;
138     }
139 }
140