2 // ManagedProtection.cs -
3 // Protect (encrypt) data without (user involved) key management
6 // Sebastien Pouliot <sebastien@ximian.com>
8 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 using System
.Runtime
.InteropServices
;
33 using System
.Security
;
34 using System
.Security
.Cryptography
;
35 using System
.Security
.Permissions
;
37 namespace Mono
.Security
.Cryptography
{
39 // Managed Protection Implementation
42 // * Separate RSA 1536 bits keypairs for each user and the computer
43 // * AES 128 bits encryption (separate key for each data protected)
44 // * SHA256 digest to ensure integrity
47 internal enum DataProtectionScope
{
53 internal static class ManagedProtection
{
55 // FIXME [KeyContainerPermission (SecurityAction.Assert, KeyContainerName = "DAPI",
56 // Flags = KeyContainerPermissionFlags.Open | KeyContainerPermissionFlags.Create)]
57 public static byte[] Protect (byte[] userData
, byte[] optionalEntropy
, DataProtectionScope scope
)
60 throw new ArgumentNullException ("userData");
62 Rijndael aes
= Rijndael
.Create ();
65 byte[] encdata
= null;
66 using (MemoryStream ms
= new MemoryStream ()) {
67 ICryptoTransform t
= aes
.CreateEncryptor ();
68 using (CryptoStream cs
= new CryptoStream (ms
, t
, CryptoStreamMode
.Write
)) {
69 cs
.Write (userData
, 0, userData
.Length
);
71 encdata
= ms
.ToArray ();
79 SHA256 hash
= SHA256
.Create ();
84 secret
= new byte[1 + 1 + 16 + 1 + 16 + 1 + 32];
86 byte[] digest
= hash
.ComputeHash (userData
);
87 if ((optionalEntropy
!= null) && (optionalEntropy
.Length
> 0)) {
88 // the same optionalEntropy will be required to get the data back
89 byte[] mask
= hash
.ComputeHash (optionalEntropy
);
90 for (int i
= 0; i
< 16; i
++) {
92 iv
[i
] ^
= mask
[i
+ 16];
94 secret
[0] = 2; // entropy
96 secret
[0] = 1; // without entropy
99 secret
[1] = 16; // key size
100 Buffer
.BlockCopy (key
, 0, secret
, 2, 16);
101 secret
[18] = 16; // iv size
102 Buffer
.BlockCopy (iv
, 0, secret
, 19, 16);
103 secret
[35] = 32; // digest size
104 Buffer
.BlockCopy (digest
, 0, secret
, 36, 32);
106 RSAOAEPKeyExchangeFormatter formatter
= new RSAOAEPKeyExchangeFormatter (GetKey (scope
));
107 header
= formatter
.CreateKeyExchange (secret
);
111 Array
.Clear (key
, 0, key
.Length
);
114 if (secret
!= null) {
115 Array
.Clear (secret
, 0, secret
.Length
);
119 Array
.Clear (iv
, 0, iv
.Length
);
126 byte[] result
= new byte[header
.Length
+ encdata
.Length
];
127 Buffer
.BlockCopy (header
, 0, result
, 0, header
.Length
);
128 Buffer
.BlockCopy (encdata
, 0, result
, header
.Length
, encdata
.Length
);
132 // FIXME [KeyContainerPermission (SecurityAction.Assert, KeyContainerName = "DAPI",
133 // Flags = KeyContainerPermissionFlags.Open | KeyContainerPermissionFlags.Decrypt)]
134 public static byte[] Unprotect (byte[] encryptedData
, byte[] optionalEntropy
, DataProtectionScope scope
)
136 if (encryptedData
== null)
137 throw new ArgumentNullException ("encryptedData");
139 byte[] decdata
= null;
141 Rijndael aes
= Rijndael
.Create ();
142 RSA rsa
= GetKey (scope
);
143 int headerSize
= (rsa
.KeySize
>> 3);
144 bool valid1
= (encryptedData
.Length
>= headerSize
);
146 headerSize
= encryptedData
.Length
;
148 byte[] header
= new byte[headerSize
];
149 Buffer
.BlockCopy (encryptedData
, 0, header
, 0, headerSize
);
151 byte[] secret
= null;
157 SHA256 hash
= SHA256
.Create ();
161 RSAOAEPKeyExchangeDeformatter deformatter
= new RSAOAEPKeyExchangeDeformatter (rsa
);
162 secret
= deformatter
.DecryptKeyExchange (header
);
163 valid2
= (secret
.Length
== 68);
170 secret
= new byte[68];
172 // known values for structure (version 1 or 2)
173 valid3
= ((secret
[1] == 16) && (secret
[18] == 16) && (secret
[35] == 32));
176 Buffer
.BlockCopy (secret
, 2, key
, 0, 16);
178 Buffer
.BlockCopy (secret
, 19, iv
, 0, 16);
180 if ((optionalEntropy
!= null) && (optionalEntropy
.Length
> 0)) {
181 // the decrypted data won't be valid if the entropy isn't
182 // the same as the one used to protect (encrypt) it
183 byte[] mask
= hash
.ComputeHash (optionalEntropy
);
184 for (int i
= 0; i
< 16; i
++) {
186 iv
[i
] ^
= mask
[i
+ 16];
188 valid3
&= (secret
[0] == 2); // with entropy
190 valid3
&= (secret
[0] == 1); // without entropy
193 using (MemoryStream ms
= new MemoryStream ()) {
194 ICryptoTransform t
= aes
.CreateDecryptor (key
, iv
);
195 using (CryptoStream cs
= new CryptoStream (ms
, t
, CryptoStreamMode
.Write
)) {
197 cs
.Write (encryptedData
, headerSize
, encryptedData
.Length
- headerSize
);
201 // whatever, we keep going
204 decdata
= ms
.ToArray ();
207 byte[] digest
= hash
.ComputeHash (decdata
);
209 for (int i
=0; i
< 32; i
++) {
210 if (digest
[i
] != secret
[36 + i
])
216 Array
.Clear (key
, 0, key
.Length
);
219 if (secret
!= null) {
220 Array
.Clear (secret
, 0, secret
.Length
);
224 Array
.Clear (iv
, 0, iv
.Length
);
231 // single point of error (also limits timing informations)
232 if (!valid1
|| !valid2
|| !valid3
|| !valid4
) {
233 if (decdata
!= null) {
234 Array
.Clear (decdata
, 0, decdata
.Length
);
237 throw new CryptographicException (Locale
.GetText ("Invalid data."));
244 private static RSA user
;
245 private static RSA machine
;
247 private static RSA
GetKey (DataProtectionScope scope
)
250 case DataProtectionScope
.CurrentUser
:
252 CspParameters csp
= new CspParameters ();
253 csp
.KeyContainerName
= "DAPI";
254 user
= new RSACryptoServiceProvider (1536, csp
);
257 case DataProtectionScope
.LocalMachine
:
258 if (machine
== null) {
259 CspParameters csp
= new CspParameters ();
260 csp
.KeyContainerName
= "DAPI";
261 csp
.Flags
= CspProviderFlags
.UseMachineKeyStore
;
262 machine
= new RSACryptoServiceProvider (1536, csp
);
266 throw new CryptographicException (Locale
.GetText ("Invalid scope."));