Dışarıya açık dosya paylaşmada URL i zaman bazlı kısıtlamak için oluşturulmuş kod parçaları.
EncryptTool.cs
using System;
using System.Security.Cryptography;
using System.Text;
namespace SampleSigner
{
public static class EncryptTool
{
public static byte[] EncryptSha5126(string plainText)
{
if (string.IsNullOrEmpty(plainText))
{
throw new ArgumentNullException(nameof(plainText));
}
using (var sha = SHA512.Create())
return sha.ComputeHash(Encoding.UTF8.GetBytes(plainText));
}
/// <summary>
///
/// </summary>
/// <param name="plainText"> Not Null</param>
/// <param name="key">byte[64] random bytes</param>
/// <returns></returns>
public static byte[] HmacSha256(string plainText, byte[] key)
{
if (key == null)
throw new ArgumentNullException(nameof(key));
if (key.Length != 64)
throw new ArgumentException("key.length must be 64");
if (string.IsNullOrEmpty(plainText))
throw new ArgumentNullException(nameof(plainText));
using (HMACSHA256 hmac = new HMACSHA256(key))
{
return hmac.ComputeHash(Encoding.UTF8.GetBytes(plainText));
}
}
}
}
// SignedStringSettings.cs
using System;
namespace SampleSigner
{
public class SignedStringSettings
{
/// <summary>
/// Key olarak proje her başlatıldığında key değişsin diye dinamik guid üretilmiştir.
/// İsteyen gömülü key kullanabilir.
/// Ben dosya paylaşım süresini uzun tutmayacağım için Guid benim için sorun değil
/// </summary>
private SignedStringSettings()
:this(Guid.NewGuid().ToString())
{
}
private SignedStringSettings(string key)
{
var bytes = EncryptTool.EncryptSha5126(key);
byte[] data = new byte[64];
Array.Copy(bytes, 0, data, 0, 64);
Key = data;
}
public static readonly Lazy<SignedStringSettings> _instance
= new Lazy<SignedStringSettings>(() => new SignedStringSettings());
public static SignedStringSettings Instance => _instance.Value;
public byte[] Key { get; private set; }
}
}
// SignedString.cs
using System;
using System.Security.Cryptography;
using System.Text;
namespace SampleSigner
{
public class SignedString
{
private string _text { get; set; }
private long _validUntil { get; set; }
private SignedString(string text, DateTimeOffset validUntil)
{
_validUntil = validUntil.ToUnixTimeSeconds();
_text = Sign(text, _validUntil);
}
private SignedString(string text)
{
_text = text;
}
public static SignedString CreateNew(string text, DateTimeOffset to)
{
return new SignedString(text, to);
}
public static SignedString CreateNew(string text, TimeSpan time)
{
return new SignedString(text, DateTimeOffset.UtcNow.Add(time));
}
public static SignedString Load(string text)
{
return new SignedString(text);
}
private string Sign(string text, long validUntil)
{
var plainText = Convert.ToBase64String(Encoding.UTF8.GetBytes(text));
plainText += $".{validUntil}";
var computedHash = EncryptTool.HmacSha256(plainText, SignedStringSettings.Instance.Key);
return $"{plainText}.{Convert.ToBase64String(computedHash)}";
}
private bool Verify(string signedText, byte[] key)
{
if (string.IsNullOrWhiteSpace(signedText))
throw new ArgumentNullException(nameof(signedText));
var parts = signedText.Split('.');
if (parts.Length != 3
|| string.IsNullOrWhiteSpace(parts[0])
|| string.IsNullOrWhiteSpace(parts[1])
|| string.IsNullOrWhiteSpace(parts[2]))
throw new ArgumentException("Invalid String");
byte[] computedBytes = null;
using (HMACSHA256 hmac = new HMACSHA256(key))
{
var checkText = signedText.Substring(0, signedText.LastIndexOf("."));
computedBytes = EncryptTool.HmacSha256(checkText, SignedStringSettings.Instance.Key);
}
var signBytes = Convert.FromBase64String(parts[2]);
if (computedBytes == null
|| (computedBytes.Length != signBytes.Length))
return false;
for (int i = 0; i < computedBytes.Length; i++)
{
if (computedBytes[i] != signBytes[i])
{
return false;
};
}
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var until = Convert.ToInt64(parts[1]);
return now < until;
}
public override string ToString()
{
return _text;
}
public bool IsValid => Verify(_text, SignedStringSettings.Instance.Key);
public string ToUnsignedString()
{
if (IsValid)
{
var parts = _text.Split(".");
var byteText = Convert.FromBase64String(parts[0]);
return Encoding.UTF8.GetString(byteText);
}
else return string.Empty;
}
}
}
// Program.cs
using System;
using System.Threading;
namespace SampleSigner
{
internal class Program
{
static void Main(string[] args)
{
var plainUrl = "https://www.muharrembarkin.com/";
var sample = SignedString.CreateNew(plainUrl, TimeSpan.FromSeconds(5));
// Url is valid 5 seconds
var signedUrl = sample.ToString();
Console.WriteLine($"Signed Url: {sample}");
Console.WriteLine();
// 1 second later valid
Thread.Sleep(1000);
var sample2 = SignedString.Load(signedUrl);
Console.WriteLine($"Is Valid after 1 seconds: {sample2.IsValid}");
Console.WriteLine();
Console.WriteLine("1 Second later Unsigned Url: " + sample2.ToUnsignedString());
Console.WriteLine();
// 6 second later not valid
Thread.Sleep(5000);
Console.WriteLine($"Is Valid after 6 seconds: {sample2.IsValid}");
Console.WriteLine();
Console.WriteLine("6 Second later Unsigned Url: " + sample2.ToUnsignedString());
Console.WriteLine();
var sample3 = SignedString.CreateNew(plainUrl, TimeSpan.FromSeconds(30));
Console.WriteLine($"Signed Url: {sample3}");
Console.WriteLine();
Console.WriteLine("lets change one character of signed url");
var signedUrl2 = sample3.ToString();
signedUrl2 = signedUrl2.Substring(0, signedUrl2.Length - 1) + "a";
Console.WriteLine("Changed Url: " + signedUrl2);
Console.WriteLine();
var sample4 = SignedString.Load(signedUrl2);
Console.WriteLine($"Is valid changed url: {sample4.IsValid}");
Console.WriteLine();
Console.WriteLine($"UnsignedString changed url: {sample4.ToUnsignedString()}");
Console.WriteLine();
Console.WriteLine("Re test original url");
var sample5 = SignedString.Load(sample3.ToString());
Console.WriteLine($"IsValid: {sample5.IsValid}");
Console.WriteLine($"UnsignedString: {sample5.ToUnsignedString()}");
}
}
}