I’ve observed many .NET open-source projects whose authors either don’t know about strong name signing or are actively opposed to it. In this post, I’ll explain what strong name signing is, when folks should use it, and refute several common points of misinformation out there that tends to dissuade people from strong name signing.
What is strong naming
Microsoft describes strong naming in their doc Strong naming and .NET libraries – .NET. In short, it changes an assembly name from a simple “MyUtilities, Version=1.0.0.0, Culture=neutral” to a longer “MyUtilities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a4e8f1e6cbfd650b“.
The added PublicKeyToken is a hash for a much longer PublicKey, which has a companion private key that is used to sign the assembly. A strong named assembly is typically signed with this private key, but not always. More on that later.
Strong naming is free. While authenticode signing requires purchasing a code signing certificate, strong name signing does not. These two signing methods address different needs and should not be looked at as alternatives, since they may be used together.
Why strong name?
TLDR: Everyone can reference a strong named assembly. But a non-strong named assembly can only be referenced by other non-strong named assemblies.
Microsoft list several reasons for Why strong-name your assemblies? that I suggest you review.
In their official guidance, the .NET team espouses strong naming for avoiding assembly naming conflicts:
Strong naming refers to signing an assembly with a key, producing a strong-named assembly. When an assembly is strong-named, it creates a unique identity based on the name and assembly version number, and it can help prevent assembly conflicts.
This means if two people produce assemblies called Utilities.dll, .NET Framework can load both of them without getting confused. It still puts an onus on the app developer to deploy these two assemblies to different directories so they don’t clash in the file system, and have an .exe.config file that points to each of them, but it’s only possible if they are strong named. You should still name your assemblies uniquely to avoid conflicts when possible.
Strong naming isn’t about security. The .NET team has said as much officially. They further state:
✔️ CONSIDER adding the strong naming key to your source control system.
A publicly available key lets developers modify and recompile your library source code with the same key.
And…
When the identity of the publisher of the code is desired, Authenticode and NuGet Package Signing are recommended.
And in their sn tool documentation…
Do not rely on strong names for security. They provide a unique identity only.
So you see, strong naming is not for verifying the authenticity of the publisher. Therefore, disclosing the .snk publicly in an OSS repo is perfectly fine. You lose nothing as an OSS non-strong-named library by adding a strong name (except binary compatibility with previous versions of your library when a user is running on .NET Framework). You didn’t have publisher security before, and you don’t after. But you gain a whole set of .NET Framework customers who already strong name their assemblies and are technically prevented from referencing assemblies that are not strong named.
Many OSS repos (including all of my own) check in the .snk for their own use and to maintain the spirit of OSS, which is that anyone can modify and rebuild an assembly and use it in place of the original. Strong naming and hiding the private key would violate that, but Strong naming and disclosing the private key maintains that.
Strong Name signing verification
.NET Framework usually doesn’t verify the strong name signature at all, and .NET never does. This further confirms that strong naming isn’t about security. .NET Framework only verifies a strong name signature when an assembly is ngen’d, installed in the GAC, or shadow copied (and a few other less common scenarios). None of these happen in the normal process of loading and executing an assembly in an application.
Because these runtimes so rarely (or never) verify the signature, the C# compiler added a switch to do what it calls public signing. With public signing, a public key is used to strong name an assembly, but its signature is all zeros, and the private key is never used. This is an invalid signature, but it works fine on .NET. It also works on .NET Framework so long as the assembly is not GAC’d, ngen’d, or shadow copied, etc. If you release assemblies for others to use, please use properly signed as
For completeness, I’ll mention that another concept called delay signing also exists. There are precious few reasons to do this and it significantly complicates a build. I do not advocate anyone to start doing this.
Caveats
A strong named assembly has a couple limitations:
- It can only reference other strong named assemblies.
- It can only expose its internals via InternalsVisibleTo to other strong named assemblies.
These constraints put something of a semi-viral requirement on a family of assemblies, whether they are strong named or not. If a team or product tends to strong name their assemblies, their dependencies need to as well. Similarly, if that team does not strong name their assemblies, no one downstream of them can either.
If it’s viral either way, how do you decide which side to be on? Well, as an individual assembly author, you gain more audience by being strong named. You only win users by strong naming, because everyone can reference a strong named assembly, while only a subset of users can reference your library if you don’t strong name.
Counter-arguments and rebuttals
Claim: “Only enterprises strong name their assemblies.”
Fact check: false. For reasons given above, many developers including OSS developers strong name their assemblies to increase their audience.
Claim: “If someone wants to use my library, they can strong name it themselves, or pay me to strong name a private copy for them.”
There are a couple tools out there that automate adding a strong name to an existing assembly. There are significant downsides to this, however. Applying a strong name to an existing assembly changes its identity for the CLR, so any other assembly that referenced the original will refuse to run against the strong named version.
Claim: “I can ship a strong named and non-strong named assembly in separate nuget packages.”
This is about as disastrous as can be. Consider you ship package Z and Z.Signed. Along comes nuget package M which is strong named so it depends on Z.Signed. Package N is not strong named and decides to depend on Z. Now app or package A comes along and consumes M and N. Now we have a problem, because Z.dll is in the dependency graph twice — once with a strong name and once without. The compiler of A.exe or A.dll gets all the types in Z.dll twice and you get compile errors. You also have deployment issues because msbuild tries to copy Z.dll twice. And .NET won’t be able to load both anyways.
Please do not ship both strong named and non-strong named assemblies. Just add a strong name to your existing assembly and bump the major version number (because it’s a binary breaking change for .NET Framework users).
Strong naming and .NET
While .NET Framework considers a strong name to be a crucial part of an assembly’s name, .NET (as in Core CLR) does not. By default, .NET disregards a strong name completely. This has a couple ramifications:
- Strong naming is not a means to load multiple assemblies with the same name but from different publishers into the same process for a .NET app.
- As .NET Framework gives way to .NET, the call to strong name assemblies becomes less relevant. However, there are still many folks who run on or at least support .NET Framework, which is still fully supported by Windows. So this is a long way off.
How to start strong naming your assembly
Giving your assembly a strong name is easy and free. First, generate a strong name signing key with the sn
tool:
sn -k strongname.snk
The sn
tool comes with the .NET Framework SDK, which you can freely install if you have a Windows machine handy. Or on a non-Windows machine you can install mono to get sn
as well. The .snk file that you generate will work on any OS and to build any assembly with the .NET SDK via the dotnet build
.
Go ahead and check in the .snk file you generate into your source control system. Yes, it contains a private key, but as stated above, strong naming isn’t about security so sharing this private key publicly is just fine. I do this for all my OSS repos.
Now add two properties to your project.
<PropertyGroup>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>strongname.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
Alternatively, you can define these properties in your Directory.Build.props file at the root of your repo to strong name sign all your projects at once. Move the strongname.snk file to the root of your repo and then add these properties:
<PropertyGroup>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)strongname.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
At this point, you may be done. Try building. The build may fail for a couple reasons:
- Your projects reference assemblies that are not yet strong named. If these are 3rd party dependencies, file bugs against them asking them politely to strong name their assemblies. Link to this article to help alleviate their concerns and instruct them on how to proceed. Your progress in strong naming your assembly is blocked until your dependencies strong name or you drop those dependencies.
- You have
InternalsVisibleTo
attributes in your project that need to be updated to include the PublicKey of the referencing assemblies (which now also must be strong named). Since you presumably have control over the assemblies that you are offering internals visibility to, you should be able to strong name these as well and then update your attribute. Getting the value to use in PublicKey can be tough at the command line. But in a C#-aware IDE like Visual Studio, a completion list is offered that makes updating these attributes really easy. And if you’re using Nerdbank.GitVersioning you can just referenceThisAssembly.PublicKey
to get the value:[InternalsVisibleTo("My.Tests, PublicKey=" + ThisAssembly.PublicKey)]
Note that this is the public key itself rather than the much shorter public key token seen in an assembly’s strong name.
Congratulations on strong naming your assembly. And thank you for making your assemblies available to a broader audience.