2 // PrivateKey.cs - Private Key (PVK) Format Implementation
5 // Sebastien Pouliot <sebastien@ximian.com>
7 // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
8 // Copyright (C) 2004 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.
31 using System
.Globalization
;
33 using System
.Security
.Cryptography
;
36 using Mono
.Security
.Cryptography
;
38 namespace Mono
.Security
.Authenticode
{
41 // a. http://www.drh-consultancy.demon.co.uk/pvk.html
43 public class PrivateKey
{
45 private const uint magic
= 0xb0b5f11e;
47 private bool encrypted
;
54 keyType
= 2; // required for MS makecert !!!
57 public PrivateKey (byte[] data
, string password
)
60 throw new ArgumentNullException ("data");
62 if (!Decode (data
, password
)) {
63 throw new CryptographicException (
64 Locale
.GetText ("Invalid data and/or password"));
68 public bool Encrypted
{
69 get { return encrypted; }
73 get { return keyType; }
74 set { keyType = value; }
83 get { return ((encrypted) ? weak : true); }
87 private byte[] DeriveKey (byte[] salt
, string password
)
89 byte[] pwd
= Encoding
.ASCII
.GetBytes (password
);
90 SHA1 sha1
= (SHA1
)SHA1
.Create ();
91 sha1
.TransformBlock (salt
, 0, salt
.Length
, salt
, 0);
92 sha1
.TransformFinalBlock (pwd
, 0, pwd
.Length
);
93 byte[] key
= new byte [16];
94 Buffer
.BlockCopy (sha1
.Hash
, 0, key
, 0, 16);
96 Array
.Clear (pwd
, 0, pwd
.Length
);
100 private bool Decode (byte[] pvk
, string password
)
103 if (BitConverterLE
.ToUInt32 (pvk
, 0) != magic
)
106 if (BitConverterLE
.ToUInt32 (pvk
, 4) != 0x0)
109 keyType
= BitConverterLE
.ToInt32 (pvk
, 8);
111 encrypted
= (BitConverterLE
.ToUInt32 (pvk
, 12) == 1);
113 int saltlen
= BitConverterLE
.ToInt32 (pvk
, 16);
115 int keylen
= BitConverterLE
.ToInt32 (pvk
, 20);
116 byte[] keypair
= new byte [keylen
];
117 Buffer
.BlockCopy (pvk
, 24 + saltlen
, keypair
, 0, keylen
);
118 // read salt (if present)
120 if (password
== null)
123 byte[] salt
= new byte [saltlen
];
124 Buffer
.BlockCopy (pvk
, 24, salt
, 0, saltlen
);
125 // first try with full (128) bits
126 byte[] key
= DeriveKey (salt
, password
);
127 // decrypt in place and try this
128 RC4 rc4
= RC4
.Create ();
129 ICryptoTransform dec
= rc4
.CreateDecryptor (key
, null);
130 dec
.TransformBlock (keypair
, 8, keypair
.Length
- 8, keypair
, 8);
132 rsa
= CryptoConvert
.FromCapiPrivateKeyBlob (keypair
);
135 catch (CryptographicException
) {
137 // second chance using weak crypto
138 Buffer
.BlockCopy (pvk
, 24 + saltlen
, keypair
, 0, keylen
);
139 // truncate the key to 40 bits
140 Array
.Clear (key
, 5, 11);
142 RC4 rc4b
= RC4
.Create ();
143 dec
= rc4b
.CreateDecryptor (key
, null);
144 dec
.TransformBlock (keypair
, 8, keypair
.Length
- 8, keypair
, 8);
145 rsa
= CryptoConvert
.FromCapiPrivateKeyBlob (keypair
);
147 Array
.Clear (key
, 0, key
.Length
);
151 // read unencrypted keypair
152 rsa
= CryptoConvert
.FromCapiPrivateKeyBlob (keypair
);
153 Array
.Clear (keypair
, 0, keypair
.Length
);
156 // zeroize pvk (which could contain the unencrypted private key)
157 Array
.Clear (pvk
, 0, pvk
.Length
);
159 return (rsa
!= null);
162 public void Save (string filename
)
164 Save (filename
, null);
167 public void Save (string filename
, string password
)
169 if (filename
== null)
170 throw new ArgumentNullException ("filename");
173 FileStream fs
= File
.Open (filename
, FileMode
.Create
, FileAccess
.Write
);
176 byte[] empty
= new byte [4];
177 byte[] data
= BitConverterLE
.GetBytes (magic
);
178 fs
.Write (data
, 0, 4); // magic
179 fs
.Write (empty
, 0, 4); // reserved
180 data
= BitConverterLE
.GetBytes (keyType
);
181 fs
.Write (data
, 0, 4); // key type
183 encrypted
= (password
!= null);
184 blob
= CryptoConvert
.ToCapiPrivateKeyBlob (rsa
);
186 data
= BitConverterLE
.GetBytes (1);
187 fs
.Write (data
, 0, 4); // encrypted
188 data
= BitConverterLE
.GetBytes (16);
189 fs
.Write (data
, 0, 4); // saltlen
190 data
= BitConverterLE
.GetBytes (blob
.Length
);
191 fs
.Write (data
, 0, 4); // keylen
193 byte[] salt
= new byte [16];
194 RC4 rc4
= RC4
.Create ();
197 // generate new salt (16 bytes)
198 RandomNumberGenerator rng
= RandomNumberGenerator
.Create ();
200 fs
.Write (salt
, 0, salt
.Length
);
201 key
= DeriveKey (salt
, password
);
203 Array
.Clear (key
, 5, 11);
204 ICryptoTransform enc
= rc4
.CreateEncryptor (key
, null);
205 // we don't encrypt the header part of the BLOB
206 enc
.TransformBlock (blob
, 8, blob
.Length
- 8, blob
, 8);
209 Array
.Clear (salt
, 0, salt
.Length
);
210 Array
.Clear (key
, 0, key
.Length
);
215 fs
.Write (empty
, 0, 4); // encrypted
216 fs
.Write (empty
, 0, 4); // saltlen
217 data
= BitConverterLE
.GetBytes (blob
.Length
);
218 fs
.Write (data
, 0, 4); // keylen
221 fs
.Write (blob
, 0, blob
.Length
);
224 // BLOB may include an uncrypted keypair
225 Array
.Clear (blob
, 0, blob
.Length
);
230 static public PrivateKey
CreateFromFile (string filename
)
232 return CreateFromFile (filename
, null);
235 static public PrivateKey
CreateFromFile (string filename
, string password
)
237 if (filename
== null)
238 throw new ArgumentNullException ("filename");
241 using (FileStream fs
= File
.Open (filename
, FileMode
.Open
, FileAccess
.Read
, FileShare
.Read
)) {
242 pvk
= new byte [fs
.Length
];
243 fs
.Read (pvk
, 0, pvk
.Length
);
246 return new PrivateKey (pvk
, password
);