Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New Feature]: Identify Package Family Name from MSIX without installing #180

Closed
russellbanks opened this issue May 9, 2023 · 1 comment · Fixed by microsoft/winget-pkgs#129186
Assignees
Labels
Enhancement New feature or request

Comments

@russellbanks
Copy link

russellbanks commented May 9, 2023

Description of the new feature/enhancement

The MSIX family name can be calculated without installing it.

  1. Treat the MSIX like a zip.
  2. Get the identity name and identity publisher from the MSIX manifest.
  3. Implement package family name algorithm.

Komac's implementation:

private const val hex255 = 0xFF
private const val binaryRadix = 2
private const val bitGroupsSize = 5
private const val padLength = 8

/**
 * Generates the package family name for a given identity name and identity publisher.
 *
 * The algorithm takes the following steps:
 * 1. Calculate the SHA-256 hash of the byte representation of the UTF-16 identity publisher.
 * 2. Take the first 8 bytes (64 bits) of the SHA-256 hash.
 * 3. Concatenate each byte of the first 8 bytes, and convert them to binary representation.
 * 4. Pad the binary value by a single zero bit to the right (left shift all bits).
 * 5. Group the bits in groups of 5.
 * 6. For each group, convert the bit representation to an index of the string "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
 * 7. Join the letters together and make them lowercase.
 * 8. Append the hash part to the identity name with an underscore as a separator.
 *
 * @param identityName a string representing the identity name.
 * @param identityPublisher a UTF-16 string representing the identity publisher.
 * @return the package family name generated using the algorithm.
 */
fun getPackageFamilyName(identityName: String, identityPublisher: String): String {
    val hashPart = identityPublisher.encode(Charsets.UTF_16LE)
        .sha256()
        .substring(0, 8)
        .toByteArray()
        .map { it.toInt() and hex255 }
        .joinToString("") { it.toString(binaryRadix).padStart(padLength, '0') }
        .plus('0')
        .chunked(bitGroupsSize)
        .map { "0123456789ABCDEFGHJKMNPQRSTVWXYZ"[it.toInt(binaryRadix)] }
        .joinToString("")
        .lowercase()
    return "${identityName}_$hashPart"
}

The big thing to note is that 0123456789ABCDEFGHJKMNPQRSTVWXYZ does not include all the letters of the alphabet. It has to be exactly that.

Proposed technical implementation details (optional)

Useful sources:

  1. https://marcinotorowski.com/2021/12/19/calculating-hash-part-of-msix-package-family-name
  2. https://www.tmurgent.com/TmBlog/?p=3270
@russellbanks
Copy link
Author

From https://marcinotorowski.com/2021/12/19/calculating-hash-part-of-msix-package-family-name:

function Get-PublisherHash($publisherName)
{
    $publisherNameAsUnicode = [System.Text.Encoding]::Unicode.GetBytes($publisherName);
    $publisherSha256 = [System.Security.Cryptography.HashAlgorithm]::Create("SHA256").ComputeHash($publisherNameAsUnicode);
    $publisherSha256First8Bytes = $publisherSha256 | Select-Object -First 8;
    $publisherSha256AsBinary = $publisherSha256First8Bytes | ForEach-Object { [System.Convert]::ToString($_, 2).PadLeft(8, '0') };
    $asBinaryStringWithPadding = [System.String]::Concat($publisherSha256AsBinary).PadRight(65, '0');
 
    $encodingTable = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
 
    $result = "";
    for ($i = 0; $i -lt $asBinaryStringWithPadding.Length; $i += 5)
    {
        $asIndex = [System.Convert]::ToInt32($asBinaryStringWithPadding.Substring($i, 5), 2);
        $result += $encodingTable[$asIndex];
    }
 
    return $result.ToLower();
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants