Files
stream-cinema/Webshare/LinkGenerator.cs

139 lines
4.5 KiB
C#

using System;
using System.Security.Cryptography;
using System.Text;
namespace JellyfinCinemaStream.Webshare;
public class LinkGenerator
{
private static readonly BaseEncoding UnixMD5 = new BaseEncoding("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", false);
/// <summary>
/// Calculates the Webshare download password from download identifier, name and salt.
/// </summary>
/// <param name="downloadId">Webshare download identifier.</param>
/// <param name="downloadName">Webshare download name.</param>
/// <param name="salt">Download salt obtained from https://webshare.cz/api/file_password_salt/.</param>
/// <returns>Password to use to obtain a download link using https://webshare.cz/api/file_link/.</returns>
public static string CalculatePassword(string downloadId, string downloadName, string salt)
{
if (downloadId == null || downloadName == null || salt == null)
throw new ArgumentNullException();
// Example
// name = "bIaFGdkvKiiTBo2_S3E"
// id = "12kRBqAYQS2"
// password = sha256(name + id) = "1e98873b57b7660cba7267c191f2d1fb7d198d062eb2fe0686502883042c4105"
// crypt = md5crypt(password, salt: "UX8y8Fpa") = "$1$UX8y8Fpa$stY.bUYGBsmAlc6IIxnHU0"
// result = sha1(crypt) = "8ceff8e5abf9aa375a8a5b05871b6cd6fb7fd185")
string toHash = downloadName + downloadId;
byte[] password = SHA256.HashData(Encoding.UTF8.GetBytes(toHash));
string passwordS = Convert.ToHexStringLower(password);
string crypt = MD5Crypt(passwordS, salt);
byte[] result = SHA1.HashData(Encoding.ASCII.GetBytes(crypt));
return Convert.ToHexStringLower(result);
}
private static string MD5Crypt(string password, string salt)
{
string prefixString = "$1$";
byte[] prefixBytes = Encoding.ASCII.GetBytes(prefixString);
byte[] saltBytes = Encoding.ASCII.GetBytes(salt);
byte[] key = Encoding.ASCII.GetBytes(password);
byte[] truncatedSalt = TruncateAndCopy(saltBytes, 8);
byte[] crypt = Crypt(key, truncatedSalt, prefixBytes, MD5.Create());
string result = prefixString
+ salt + '$'
+ UnixMD5.GetString(crypt);
return result.TrimEnd('=');
}
private static byte[] Crypt(byte[] key, byte[] salt, byte[] prefix, HashAlgorithm A)
{
byte[] H = null, I = null;
A.Initialize();
AddToDigest(A, key);
AddToDigest(A, salt);
AddToDigest(A, key);
FinishDigest(A);
I = (byte[])A.Hash.Clone();
A.Initialize();
AddToDigest(A, key);
AddToDigest(A, prefix);
AddToDigest(A, salt);
AddToDigestRolling(A, I, 0, I.Length, key.Length);
int length = key.Length;
for (int i = 0; i < 31 && length != 0; i++)
{
AddToDigest(A, new[] { (length & (1 << i)) != 0 ? (byte)0 : key[0] });
length &= ~(1 << i);
}
FinishDigest(A);
H = (byte[])A.Hash.Clone();
for (int i = 0; i < 1000; i++)
{
A.Initialize();
if ((i & 1) != 0) { AddToDigest(A, key); }
if ((i & 1) == 0) { AddToDigest(A, H); }
if ((i % 3) != 0) { AddToDigest(A, salt); }
if ((i % 7) != 0) { AddToDigest(A, key); }
if ((i & 1) != 0) { AddToDigest(A, H); }
if ((i & 1) == 0) { AddToDigest(A, key); }
FinishDigest(A);
Array.Copy(A.Hash, H, H.Length);
}
byte[] crypt = new byte[H.Length];
int[] permutation = new[] { 11, 4, 10, 5, 3, 9, 15, 2, 8, 14, 1, 7, 13, 0, 6, 12 };
Array.Reverse(permutation);
for (int i = 0; i < crypt.Length; i++)
{
crypt[i] = H[permutation[i]];
}
return crypt;
}
private static void AddToDigest(HashAlgorithm algorithm, byte[] buffer)
{
AddToDigest(algorithm, buffer, 0, buffer.Length);
}
private static void AddToDigest(HashAlgorithm algorithm, byte[] buffer, int offset, int count)
{
algorithm.TransformBlock(buffer, offset, count, buffer, offset);
}
private static void AddToDigestRolling(HashAlgorithm algorithm, byte[] buffer, int offset, int inputCount, int outputCount)
{
int count;
for (count = 0; count < outputCount; count += inputCount)
{
AddToDigest(algorithm, buffer, offset, Math.Min(outputCount - count, inputCount));
}
}
private static void FinishDigest(HashAlgorithm algorithm)
{
algorithm.TransformFinalBlock(new byte[0], 0, 0);
}
private static byte[] TruncateAndCopy(byte[] buffer, int maxLength)
{
byte[] truncatedBuffer = new byte[Math.Min(buffer.Length, maxLength)];
Array.Copy(buffer, truncatedBuffer, truncatedBuffer.Length);
return truncatedBuffer;
}
}