I am trying to receive the user's password from a PDF file. For testing purposes I'm given both, the master's and user's password. Right now I am passing the master's password in a parameter and use it to create a new instance of iTextSharp.text.pdf.PdfReader which works fine. Then I'm entering an if-clause which should return whether the PDF is being opened with full permissions or not. Inside this if-clause I request the user's password by calling iTextSharp.text.pdf.PdfReader.ComputeUserPassword() which returns null.
My entire code looks like this (GetByteAr(string s) returns the password converted to a byte array):
public static bool IsPasswordProtectedOwner(string pdf, string ownerPw)
{
try
{
var reader = new PdfReader(pdf, GetByteAr(ownerPw));
if (reader.IsOpenedWithFullPermissions)
{
Console.WriteLine("opened with full permissions");
string pw = String.Empty;
var computedPassword = reader.ComputeUserPassword();
foreach (byte b in computedPassword)
pw += Char.ConvertFromUtf32(b);
}
else
{
Console.WriteLine("not opened with full permissions");
}
}
catch (Exception e) when (e is NullReferenceException || e is BadPasswordException)
{
Console.WriteLine(e);
}
return true;
}
And my output looks like this:
opened with full permissions
System.NullReferenceException: Object reference not set to an instance of an object.
at PDFsV2.PDFInteractor.IsPasswordProtectedOwner(String pdf, String ownerPw)
in C:\Users\user\source\repos\PDFsV2\PDFsV2\PDFInteractor.cs:line 57
Can you help me understand why computedPassword is null? Why is ComputeUserPassword returning null?
Edit, this is the reason why it returns null:
https://api.itextpdf.com/iText5/5.5.13/
public byte[] computeUserPassword()
Computes user password if standard encryption handler is used with
Standard40, Standard128 or AES128 encryption algorithm.
Returns:
user password, or null if not a standard encryption handler was used, if standard encryption handler was used with AES256 encryption
algorithm, or if ownerPasswordUsed wasn't use to open the document.
https://github.com/kusl/itextsharp/blob/master/tags/iTextSharp_5_4_5/src/core/iTextSharp/text/pdf/PdfReader.cs#L3849 shows the implementation of ComputeUserPassword as:
public byte[] ComputeUserPassword() {
if (!encrypted || !ownerPasswordUsed) return null;
return decrypt.ComputeUserPassword(password);
}
As per that code (second line) it is possible for ComputeUserPassword to be null. As such, you need to cater for that in your code (i.e. check whether it is null before foreaching over it).
In your case, it is likely because:
ownerPasswordUsed = decrypt.ReadKey(enc, password);
is returning false. This may indicate you have the wrong password value.
Similarly, the docs state:
user password, or null if not a standard encryption handler was used
or if ownerPasswordUsed wasn't use to open the document.
I am new to C# ,Here in my web API project I have some code for hashing the user password using SHA3 .
In API I have 2 methods
First method is used for when new User create an account I just hashing the password and store it in the table.
The second method is for when the same user log In again I fetch the hashed password(string) and verify the current password with it .
Here I have faced the error as I mentioned in the title and I referred some related post in SO also but I could't solve this issue .
I have verified that the stored and fetched string has the same characters also the generated and stored string has the same characters.
I don't know where I did the mistakes .
Password : Abcd#123
Hashed String : k/OMmdnW6FZ+zsOrE2rkdy8YEUH/rep5dlcRIwnG8Vc7kQ81VL8dEQv2Clyp7iRhb0HSfKtgOLBj5g/YbqHq7FKDj5epafNwasE=
Calling Confirm method
bool isPasswordPassed = false;
if (mHashedPassword != " " && mUserPassword != " ")
{
isPasswordPassed = Hashing.Confirm(mUserPassword, mHashedPassword, Supported_HA.SHA512);
}
Confirm
public static bool Confirm(string plainText, string hashValue, Supported_HA hash)
{
byte[] hashBytes = Convert.FromBase64String(hashValue);//This line passing the error as in my title.
......
.......
.....
}
But It works fine when I check the code like this ...
check(mUserPassword){
string a = Hashing.ComputeHash(mUserPassword, Supported_HA.SHA512, null);
bool b = Hashing.Confirm(mUserPassword, a, Supported_HA.SHA512);
}
Here I am passing the password to generate hash and the confirm hash but it returns TRUE
Can anyone help me to solve this .
Reference : https://www.youtube.com/watch?v=0dgTf9TUDHU
It may be an encoding conversion Base64 Unicode. you should pass the encoding
var plainTextBytes = System.Text.Encoding.Unicode.GetBytes(plainText);
string hashValue = Convert.ToBase64String(plainTextBytes);
then in the Confirm method
byte[] hashBytes = Convert.FromBase64String(hashValue);
should work. Regards
I'm writing custom forms authentication for ASP.NET MVC 5 (no, I don't want to use ASP.NET Identity). I'm trying to hash my passwords using a randomly-generated salt and then hashing salt+password using SHA512. Here are the methods I've written:
private static User SetPassword(User newUser, string password)
{
var rand = RNGCryptoServiceProvider.Create();
var saltBytes = new byte[128];
var passwordBytes = Encoding.UTF8.GetBytes(password);
rand.GetNonZeroBytes(saltBytes);
var passHash = SHA512Managed.Create().ComputeHash(saltBytes.Concat(passwordBytes).ToArray());
var hash = Encoding.UTF8.GetString(passHash);
var salt = Encoding.UTF8.GetString(saltBytes);
newUser.PasswordHash = hash;
newUser.Salt = salt;
return newUser;
}
private static bool ValidatePassword(User user, string passwordTry)
{
var actualPasswordBytes = Encoding.UTF8.GetBytes(user.PasswordHash);
var passwordTryBytes = Encoding.UTF8.GetBytes(passwordTry);
var saltBytes = Encoding.UTF8.GetBytes(user.Salt);
var passwordTryHashBytes = SHA512Managed.Create().ComputeHash(saltBytes.Concat(passwordTryBytes).ToArray());
if (passwordTryHashBytes == actualPasswordBytes)
{
return true;
}
return false;
}
If I step through the code, the registration (SetPassword()) method appears to work successfully. The user's record gets set with a UTF-8 encoded password hash and salt.
If I step through the password validation method, everything also appears to be operating normally. The user's record (the salt and hash) are checked against the password try.
The problem is, when I register with a password and then try to log in as that user, the password validation fails. I'm probably not understanding how one of the Cryptography classes works... can anyone explain why this doesn't work?
The following code will always fail because both objects do not point to the same reference:
if (passwordTryHashBytes == actualPasswordBytes)
Try using LINQ's SequenceEqual() method
passwordTryHashBytes.SequenceEqual(actualPasswordBytes)
I've read a nice guide a few days ago about generating a token on the server's side to have the time of the token's creation within the token, along with "Guid.NewGuid()" 's encryption.
However, I've tried to adjust the results to have a user's username within the token, rather than the date time. I'm close, but I cannot extract the username itself, I can only receive it with some random letters after it.
Code of the ASP.NET generic handler to GENERATE the token upon identification
ASCIIEncoding encoder = new ASCIIEncoding();
if(postType == "identify")
{
byte[] binName = encoder.GetBytes(name);
byte[] key = Guid.NewGuid().ToByteArray();
string _token = Convert.ToBase64String(binName.Concat(key).ToArray());
// The above creates a token with the name and the "key", works well
}
Code of the generic handler to decrypt the token (see example for result)
if(postType == "check")
{
string _token = dict["token"] as string;
byte[] data = Convert.FromBase64String(_token);
string theCode = encoder.GetString(data); // This will get both the username and the GUID key within
context.Response.Write(jss.Serialize(new Code { eCode = theCode })); // Returns in JSON, irrelevant to the question, it works well
}
EXAMPLE: If the name would be "user", then the varialbe "theCode" would hold the value of "userXyZxYzXyZ" (while XyZ stands for the GUID's "random" key).
I think it is fair to say that my question is how to separate this GUID's key from the username upon decryption
A guid is 38 characters long, so the name will be theCode.SubString(0, theCode.Length - 38). Alternately, you can compare the current user's name with theCode: theCode.StartsWith(name).
I have devices with unique serial number (string incremetation) ex : AS1002 and AS1003.
I need to figure out an algorithm to produce a unique activation key for each serial number.
What would be the best approach for this ?
Thanks !
(This has to be done offline)
You have two things to consider here:
- Whatever key you generate must be able to be entered easily, so this eliminates some weird hash which may produce characters which will be cumbersome to type, although this can be overcome, it’s something you should consider.
- The operation as you stated must be done online
Firstly, there will be no way to say with absolute certainty that someone will not be able to decipher your key generation routine, no matter how much you attempt to obfuscate. Just do a search engine query for “Crack for Xyz software”.
This has been a long battle that will never end, hence the move to deliver software as services, i.e. online where the producer has more control over their content and can explicitly authorize and authenticate a user. In your case you want to do this offline. So in your scenario someone will attach your device to some system, and the accompanying software that you intend to write this routine on will make a check against the serial number of the device v/s user input.
Based on #sll’s answer, given the offline nature of your request. Your best, unfortunately would be to generate a set of random codes, and validate them when user’s call in.
Here is a method borrowed from another SO answer, I've added digits as well
private readonly Random _rng = new Random();
private const string _chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789"; //Added 1-9
private string RandomString(int size)
{
char[] buffer = new char[size];
for (int i = 0; i < size; i++)
{
buffer[i] = _chars[_rng.Next(_chars.Length)];
}
return new string(buffer);
}
So, generating one for each of your devices and storing them somewhere might be your only option because of the offline considerations.
This routine will produce strings like this when set to create a 10 digit string, which is reasonably random.
3477KXFBDQ
ROT6GRA39O
40HTLJPFCL
5M2F44M5CH
CAVAO780NR
8XBQ44WNUA
IA02WEWOCM
EG11L4OGFO
LP2UOGKKLA
H0JB0BA4NJ
KT8AN18KFA
Activation Key
Here is a simple structure of the activation key:
Part
Description
Data
A part of the key encrypted with a password. Contains the key expiration date and application options.
Hash
Checksum of the key expiration date, password, options and environment parameters.
Tail
The initialization vector that used to decode the data (so-called "salt").
class ActivationKey
{
public byte[] Data { get; set; } // Encrypted part.
public byte[] Hash { get; set; } // Hashed part.
public byte[] Tail { get; set; } // Initialization vector.
}
The key could represent as text format: DATA-HASH-TAIL.
For example:
KCATBZ14Y-VGDM2ZQ-ATSVYMI.
The folowing tool will use cryptographic transformations to generate and verify the key.
Generating
The algorithm for obtaining a unique activation key for a data set consists of several steps:
data collection,
getting the hash and data encryption,
converting activation key to string.
Data collection
At this step, you need to get an array of data such as serial number, device ID, expiration date, etc. This purpose can be achieved using the following
method:
unsafe byte[] Serialize(params object[] objects)
{
using (MemoryStream memory = new MemoryStream())
using (BinaryWriter writer = new BinaryWriter(memory))
{
foreach (object obj in objects)
{
if (obj == null) continue;
switch (obj)
{
case string str:
if (str.Length > 0)
writer.Write(str.ToCharArray());
continue;
case DateTime date:
writer.Write(date.Ticks);
continue;
case bool #bool:
writer.Write(#bool);
continue;
case short #short:
writer.Write(#short);
continue;
case ushort #ushort:
writer.Write(#ushort);
continue;
case int #int:
writer.Write(#int);
continue;
case uint #uint:
writer.Write(#uint);
continue;
case long #long:
writer.Write(#long);
continue;
case ulong #ulong:
writer.Write(#ulong);
continue;
case float #float:
writer.Write(#float);
continue;
case double #double:
writer.Write(#double);
continue;
case decimal #decimal:
writer.Write(#decimal);
continue;
case byte[] buffer:
if (buffer.Length > 0)
writer.Write(buffer);
continue;
case Array array:
if (array.Length > 0)
foreach (var a in array) writer.Write(Serialize(a));
continue;
case IConvertible conv:
writer.Write(conv.ToString(CultureInfo.InvariantCulture));
continue;
case IFormattable frm:
writer.Write(frm.ToString(null, CultureInfo.InvariantCulture));
continue;
case Stream stream:
stream.CopyTo(stream);
continue;
default:
try
{
int rawsize = Marshal.SizeOf(obj);
byte[] rawdata = new byte[rawsize];
GCHandle handle = GCHandle.Alloc(rawdata, GCHandleType.Pinned);
Marshal.StructureToPtr(obj, handle.AddrOfPinnedObject(), false);
writer.Write(rawdata);
handle.Free();
}
catch(Exception e)
{
// Place debugging tools here.
}
continue;
}
}
writer.Flush();
byte[] bytes = memory.ToArray();
return bytes;
}
}
Getting the hash and data encryption
This step contains the following substeps:
create an encryption engine using a password and stores the initialization vector in the Tail property.
next step, expiration date and options are encrypted and the encrypted data is saved into the Data property.
finally, the hashing engine calculates a hash based on the expiration date, password, options and environment and puts it in the Hash property.
ActivationKey Create<TAlg, THash>(DateTime expirationDate,
object password,
object options = null,
params object[] environment)
where TAlg : SymmetricAlgorithm
where THash : HashAlgorithm
{
ActivationKey activationKey = new ActivationKey();
using (SymmetricAlgorithm cryptoAlg = Activator.CreateInstance<TAlg>())
{
if (password == null)
{
password = new byte[0];
}
activationKey.Tail = cryptoAlg.IV;
using (DeriveBytes deriveBytes =
new PasswordDeriveBytes(Serialize(password), activationKey.Tail))
{
cryptoAlg.Key = deriveBytes.GetBytes(cryptoAlg.KeySize / 8);
}
expirationDate = expirationDate.Date;
long expirationDateStamp = expirationDate.ToBinary();
using (ICryptoTransform transform = cryptoAlg.CreateEncryptor())
{
byte[] data = Serialize(expirationDateStamp, options);
activationKey.Data = transform.TransformFinalBlock(data, 0, data.Length);
}
using (HashAlgorithm hashAlg = Activator.CreateInstance<THash>())
{
byte[] data = Serialize(expirationDateStamp,
cryptoAlg.Key,
options,
environment,
activationKey.Tail);
activationKey.Hash = hashAlg.ComputeHash(data);
}
}
return activationKey;
}
Converting to string
Use the ToString method to get a string containing the key text, ready to be transfering to the end user.
N-based encoding (where N is the base of the number system) was often used to convert binary data into a human-readable text. The most commonly used in
activation key is base32. The advantage of this encoding is a large alphabet consisting of numbers and letters that case insensitive. The downside is that this encoding is not implemented in the .NET standard library and you should implement it yourself. You can also use the hex encoding and base64 built into mscorlib. In my example base32 is used, but I will not give its source code here. There are many examples of base32 implementation on this site.
string ToString(ActivationKey activationKey)
{
if (activationKey.Data == null
|| activationKey.Hash == null
|| activationKey.Tail == null)
{
return string.Empty;
}
using (Base32 base32 = new Base32())
{
return base32.Encode(activationKey.Data)
+ "-" + base32.Encode(activationKey.Hash)
+ "-" + base32.Encode(activationKey.Tail);
}
}
To restore use the folowing method:
ActivationKey Parse(string text)
{
ActivationKey activationKey;
string[] items = text.Split('-');
if (items.Length >= 3)
{
using (Base32 base32 = new Base32())
{
activationKey.Data = base32.Decode(items[0]);
activationKey.Hash = base32.Decode(items[1]);
activationKey.Tail = base32.Decode(items[2]);
}
}
return activationKey;
}
Checking
Key verification is carried out using methodes GetOptions an Verify.
GetOptions checks the key and restores embeded data as byte array or null if key is not valid.
Verify just checks the key.
byte[] GetOptions<TAlg, THash>(object password = null, params object[] environment)
where TAlg : SymmetricAlgorithm
where THash : HashAlgorithm
{
if (Data == null || Hash == null || Tail == null)
{
return null;
}
try
{
using (SymmetricAlgorithm cryptoAlg = Activator.CreateInstance<TAlg>())
{
cryptoAlg.IV = Tail;
using (DeriveBytes deriveBytes =
new PasswordDeriveBytes(Serialize(password), Tail))
{
cryptoAlg.Key = deriveBytes.GetBytes(cryptoAlg.KeySize / 8);
}
using (ICryptoTransform transform = cryptoAlg.CreateDecryptor())
{
byte[] data = transform.TransformFinalBlock(Data, 0, Data.Length);
int optionsLength = data.Length - 8;
if (optionsLength < 0)
{
return null;
}
byte[] options;
if (optionsLength > 0)
{
options = new byte[optionsLength];
Buffer.BlockCopy(data, 8, options, 0, optionsLength);
}
else
{
options = new byte[0];
}
long expirationDateStamp = BitConverter.ToInt64(data, 0);
DateTime expirationDate = DateTime.FromBinary(expirationDateStamp);
if (expirationDate < DateTime.Today)
{
return null;
}
using (HashAlgorithm hashAlg =
Activator.CreateInstance<THash>())
{
byte[] hash =
hashAlg.ComputeHash(
Serialize(expirationDateStamp,
cryptoAlg.Key,
options,
environment,
Tail));
return ByteArrayEquals(Hash, hash) ? options : null;
}
}
}
}
catch
{
return null;
}
}
bool Verify<TAlg, THash>(object password = null, params object[] environment)
where TAlg : SymmetricAlgorithm
where THash : HashAlgorithm
{
try
{
byte[] key = Serialize(password);
return Verify<TAlg, THash>(key, environment);
}
catch
{
return false;
}
}
Example
Here is a full example of generating the activation key using your own combination of any amount of data - text, strings, numbers, bytes, etc.
Example of usage:
string serialNumber = "0123456789"; // The serial number.
const string appName = "myAppName"; // The application name.
// Generating the key. All the parameters passed to the costructor can be omitted.
ActivationKey activationKey = new ActivationKey(
//expirationDate:
DateTime.Now.AddMonths(1), // Expiration date 1 month later.
// Pass DateTime.Max for unlimited use.
//password:
null, // Password protection;
// this parameter can be null.
//options:
null // Pass here numbers, flags, text or other
// that you want to restore
// or null if no necessary.
//environment:
appName, serialNumber // Application name and serial number.
);
// Thus, a simple check of the key for validity is carried out.
bool checkKey = activationKey.Verify((byte[])null, appName, serialNumber);
if (!checkKey)
{
MessageBox.Show("Your copy is not activated! Please get a valid activation key.");
Application.Exit();
}
By far the most secure way to do it is to have a centralized database of (serial number, activation key) pairs and have the user activate over the internet so you can check the key locally (on the server).
In this implementation, the activation key can be completely random since it doesn't need to depend on the serial number.
You want it to be easy to check, and hard to "go backwards". You'll see a lot of suggestions for using hashing functions, those functions are easy to go one way, but hard to go backwards. Previously, I phrased that as "it is easy to turn a cow into a hamburger, but hard to turn a hamburger into a cow". In this case, a device should know its own serial number and be able to "add" (or append) some secret (usually called "salt") to the serial and then hash or encrypt it.
If you are using reversible encryption, you want to add some sort of "check digit" to the serial numbers so that if someone does figure your encryption scheme out, there is another layer for them to figure out.
An example of a function that is easy enough to "go backwards" was one I solved with Excel while trying to avoid homework.
And you probably want to make things easier for your customers by making the encoding less likely to be messed up when the activation codes are handwritten (such as you write it down from the email then walk over to where the device is and punch the letters/digits in). In many fonts, I and 1, and 0 and O are similar enough that many encodings, such as your car's VIN do not use the letters i and o (and I remember older typewriters that lacked a key for the digit 1 because you were expected to use lowercase L). In such cases, Y, 4 and 7 can appear the same depending on some handwriting. So know your audience and what are their limits.
If your device has some secured memory which can not be read by connecting an programmator or an other device -you can store some key-code and then use any hashing algorithm like MD5 or SHA-1/2 to generate hash by:
HASH(PUBLIC_SERIALNUMBER + PRIVATE_KEYCODE)
And pairs of SERIALNUMBER + KEYCODE should be stored in local DB.
In this way: (offline)
Client calling you and asking for the Activation Code
You asking for a SERIALNUMBER of particular device
Then you search for a KEYCODE by a given SERIALNUMBER in your local DB and generate Activation Code (even using MD5 this will be sacure as long KEYCODE is privately stored in your DB)
Client enter Activation Code into the device, device able to generate hash
by own SERIALNUMBER and KEYCODE and then compare to Activation Code entered by user
This could be simplified by storing activation code itself if device has a secured memory onboard (like SmartCards has). In this way you can just keep own database of SerialCode - ActivationCode pairs.
How about: Invent a password that is not revealed to the user. Then concatenate this password with the serial number and hash the combination.
Anything you do can be broken by a dedicated enough hacker. The question is not, "Can I create absolutely unbreakable security?" but "Can I create security good enough to protect against unskilled hackers and to make it not worth the effort for the skilled hackers?" If you reasonably expect to sell 10 million copies of your product, you'll be a big target and there may be lots of hackers out there who will try to break it. If you expect to sell a few hundred or maybe a few thousand copies, not so much.