Safe way to store web api credentials? - c#

My web application logs into a web api. This needs an email and password. I cannot hash these in my database because the api requires the password in plain text.
How can I store my web api credentials in a safer way than plain text, xor, or base64? Is there a 'proper' solution for this sort of thing?

Yes there is, the ProtectedData class, it lets you encrypt a object tied to a windows user acount, so if the user.config file is copied to another user/computer it will not work
In your Settings file, create two string properties named ApiUsername and ApiPassword, then click "View Code at the top and add the following functions
internal sealed partial class Settings {
private MD5 md5 = MD5.Create();
public global::System.Net.NetworkCredential ApiLogin
{
get
{
global::System.Net.NetworkCredential tmp = null;
if (ApiPassword != "")
{
tmp = new System.Net.NetworkCredential();
tmp.UserName = ApiUsername;
try
{
tmp.Password = System.Text.Encoding.UTF8.GetString(ProtectedData.Unprotect(Convert.FromBase64String(ApiPassword), md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(ApiUsername.ToUpper())), DataProtectionScope.CurrentUser));
}
catch
{
tmp.Password = "";
}
}
return tmp;
}
set
{
global::System.Net.NetworkCredential tmp2 = value;
ApiUsername = tmp2.UserName;
ApiPassword = Convert.ToBase64String(ProtectedData.Protect(System.Text.Encoding.UTF8.GetBytes(tmp2.Password), md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(tmp2.UserName.ToUpper())), DataProtectionScope.CurrentUser));
}
}
}
This will add a accessable property called ApiLogin which will contain a NetworkCredential with the decrpted password, when you save the credentials to the disk it stores it in that encrpted protected form that can't be copied to other users.
If the decryption fails it sets the password to blank in the returned credential. If you want the decrption to work on any useraccount on that single machine change the ProtectionScope to DataProtectionScope.LocalMachine.

Related

I don't understand why PdfReader.ComputeUserPassword() is returning null

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.

The domain account is locked out when certificate key is accessed with local account

When I access X509Certificate2.PublicKey or X509Certificate2.PrivateKey initialized from an object that was generated with BouncyCastle, I'm getting my domain account locked out (if I do it multiple times). It happens if I run the program on behalf of a local account with the same name but different password. Here is the code:
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using System.IO;
using System.Security.Cryptography.X509Certificates;
namespace TestCertificateConversion
{
class Program
{
static void Main(string[] args)
{
var certString = GetCertificateString();
var textReader = new StringReader(certString);
var pemReader = new PemReader(textReader);
var bcCert = pemReader.ReadObject() as Org.BouncyCastle.X509.X509Certificate;
var netCert = DotNetUtilities.ToX509Certificate(bcCert);
var netCert2 = new X509Certificate2(netCert);
var publicKey = netCert2.PublicKey; // this locks out domain account
}
private static string GetCertificateString()
{
return #"-----BEGIN CERTIFICATE-----
MIICvDCCAaSgAwIBAgIQANDHl0sFjYUG3j76dYTadzANBgkqhkiG9w0BAQsFADAQ
MQ4wDAYDVQQDDAVwYWNlbTAgFw0xNjAxMDExMjQ4MzdaGA8yMjAwMDEwMTIyNTg0
N1owEDEOMAwGA1UEAwwFcGFjZW0wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQC5AKAkYnerRUmeAX0Z+aZsX39LXTVZiUd8U6waw7Hzd9E0YA50tHWizfEI
w7IBZwXS0aiXwHqJvrslc3NNs0grwu/iYQl+FGdudKmgXVE7Riu0uFAHo6eFr0dO
o0IP3LS+dPSWarXEBLbymaXRiPJDyHLefvslcSM9UQ2BHOe7dnHh9K1h+XMKTw3Z
/3szAyABBX9hsJU/mc9XjrMNXHJXALSxTfLIPzDrfh+aJtlopWpnb6vQcXwKksyk
4hyVUfw1klhglJgN0LgBGU7Ps3oxCbOqns7fB+tzkBV1E5Q97otgvMR14qLZgc8k
NQrdMl57GaWQJl6mAP1NR1gZt2f1AgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJ
KoZIhvcNAQELBQADggEBAAEz3vJOfqao9QXPWSz8YCjbWG1FeVG0NdYpd422dC2V
Vrzlo5zrkRv5XPhBOY3o81OhUe7iByiiM9suYfXLNxxd29TBGB5amO8Yv1ZX0hS/
zvVF6QS0+zZvOiujVhfHGiJxKypqgaigI6NM80ZDKPzsRPwFLIJiZYwQ7eQUlrpt
WGgFkZC23/mSOkY6VMmO5zugeMoiXRyFq33uWLlaAr+zJtRh1IPRmkA1lJv0bkC1
SslO0oSDoT2lcvZkQ5odFKX5i1z7T/wioQqG62i8nsDSz+iZOqUyDx7bL8fIEHog
qgwizgr2aAPLO/VQKU9pRTyRNFl/GL5bi7w8NN+rLxE=
-----END CERTIFICATE-----";
}
}
}
I'm not sure what I'm doing wrong, are there any security settings I might need to change to prevent it from locking out domain accounts?
Can you check and confirm if the service account is coming in this format
I checked the .net source code and found what causes an authentication problem in X509Certificate2.PublicKey. It is a creation of a new OID object:
public PublicKey PublicKey {
[SecuritySafeCritical]
get {
if (m_safeCertContext.IsInvalid)
throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidHandle), "m_safeCertContext");
if (m_publicKey == null) {
string friendlyName = this.GetKeyAlgorithm();
byte[] parameters = this.GetKeyAlgorithmParameters();
byte[] keyValue = this.GetPublicKey();
Oid oid = new Oid(friendlyName, OidGroup.PublicKeyAlgorithm, true); // this line
m_publicKey = new PublicKey(oid, new AsnEncodedData(oid, parameters), new AsnEncodedData(oid, keyValue));
}
return m_publicKey;
}
}
The OID constructor is called with lookupFriendlyName set to 'true', which leads to FindOidInfoWithFallback function:
// Try to find OID info within a specific group, and if that doesn't work fall back to all
// groups for compatibility with previous frameworks
internal static string FindOidInfoWithFallback(uint key, string value, OidGroup group)
{
string info = FindOidInfo(key, value, group);
// If we couldn't find it in the requested group, then try again in all groups
if (info == null && group != OidGroup.All)
{
info = FindOidInfo(key, value, OidGroup.All);
}
return info;
}
The first FindOidInfo returns null and then it is called second time with OidGroup.All. Eventually it results in cryptAPI call:
CAPIMethods.CryptFindOIDInfo(dwKeyType, pvKey, dwGroupId);
From documentation:
The CryptFindOIDInfo function performs a lookup in the active
directory to retrieve the friendly names of OIDs under the following
conditions:
The key type in the dwKeyType parameter is set to CRYPT_OID_INFO_OID_KEY or CRYPT_OID_INFO_NAME_KEY.
No group identifier is specified in the dwGroupId parameter or the GroupID refers to EKU OIDs, policy OIDs or template OIDs.
It then attempts to authentication with local user account and as a result I'm getting my domain account locked. From the comments to the code I see that the second FindOidInfo call was added for compatibility with older frameworks and potentially I can remove it. Unfortunately there is no easy was to change the code since it is in the framework itself. I may try to inherit the X509Certificate2 object and rewrite PublicKey and PrivateKey, but I don't really like that idea.

Decrypt ".AspNetCore.Session" cookie in ASP.NET Core

In Asp.Net core, a cookie is created when you configure your app to app.UseSession().
By default the cookie is called ".AspNetCore.Session". Its value identifies the session to be used. Currently, I'm saving my session data on a sql server. I need to know the decrypted value of ".AspNetCore.Session" so that I can lookup the session in the database.
Is there a way to decrypt this value? I know ASP.NET must do it behind the scenes somehow.
The session source has everything, but you should need to know it, ISessionStore and IDistributedSessionStore gives you a sessionkey to use.
Rather than make an assumption about the cookie format, what is stopping you from using the store APIs?
I had to extract the private Pad function from Microsoft.AspNetCore.Session, but I was able to get what I needed:
public class DiscussionController : Controller
{
private readonly IDataProtector _dataProtector;
public DiscussionController(IDataProtectionProvider dataProtectionProvider)
{
var protectorPurpose = "whatever purpose you want";
_dataProtector = dataProtectionProvider.CreateProtector(protectorPurpose);
}
public IActionResult Index()
{
HttpContext.Request.Cookies.TryGetValue(".AspNetCore.Session", out string cookieValue);
var protectedData = Convert.FromBase64String(Pad(cookieValue));
var unprotectedData = _dataProtector.Unprotect(protectedData);
var humanReadableData = System.Text.Encoding.UTF8.GetString(unprotectedData);
return Ok();
}
private string Pad(string text)
{
var padding = 3 - ((text.Length + 3) % 4);
if (padding == 0)
{
return text;
}
return text + new string('=', padding);
}
}
The Pad function was taken from: https://github.com/aspnet/AspNetCore/blob/87629bbad906e9507026692904b6bcb5021cdd33/src/Middleware/Session/src/CookieProtection.cs#L61-L69

Encrypting and Decrypting a token with a C# ASHX handler

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).

Storing "remember me" information locally c# (total newbeginner)

I started programming in c# for a few days ago, so I am a total newbeginner at this. Based on my experience in other languages, I found it somewhat "simple".
I am building a system where users are logging in to my application, which is working. I want to have a "remember me"-setting where the information is stored locally. What is the best way to do this? I'll only save the username and the password-hash.
Edit: This is a desktop-application. The login-information is sent to a php-script simply using HttpWebRequest
You can use the ConfigurationManager Class to manage your application's settings.
you can use this function to add new Keys to your configuration file:
public bool setSetting(string pstrKey, string pstrValue)
{
Configuration objConfigFile =
ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
bool blnKeyExists = false;
foreach (string strKey in objConfigFile.AppSettings.Settings.AllKeys)
{
if (strKey == pstrKey)
{
blnKeyExists = true;
objConfigFile.AppSettings.Settings[pstrKey].Value = pstrValue;
break;
}
}
if (!blnKeyExists)
{
objConfigFile.AppSettings.Settings.Add(pstrKey, pstrValue);
}
objConfigFile.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("appSettings");
return true;
}
and then save up your username (for example)
setSetting("username", usernameTextBox.Text);
Once your application starts up, you can read the information you saved earlier from your ConfigurationManager
usernameTextBox.Text = ConfigurationManager.AppSettings["username"];
you can create Application Settings in C#
here's what you will do.
don't forget to encrypt it.
If you're using ASP .NET,you can set authentication cookie when you're logged in user by second parameter
FormsAuthentication.SetAuthCookie(model.UserName, true);
Second parameter sets cookie to your request and makes "Remeber Me" option.
What I understand from your question is, php file is server and for client you are using windows form. Your are doing some kind of HTML scrapping and displaying the result HTML in your win-form. If this is the what you are doing then
//1. Create a dictionary to store cookie collection
public static Dictionary<string, Cookie> CookieCollection { get; set; }
//2. Store cookie in that collection
foreach (Cookie clientcookie in response.Cookies)
{
if (!CookieCollection.ContainsKey("AuthCookieName"))
CookieCollection .Add(userName, clientcookie);
else
CookieCollection ["userName"] = clientcookie;
}
//3. If remember me is clicked then send the same while creating request
request.CookieContainer.Add(request.RequestUri,
new Cookie("AuthCookieName", CookieCollection ["userName"]));
Where AuthCookieName is the name of authentication cookie. The only downside is when the application exists all the cookie stored in the dictionary would be gone. The solution could be serializing the cookie and storing it in database, if remember me is checked.

Categories