API keys are both incredibly powerful and extremely vulnerable. API key exposure can result in significant damage, both to a company and to the data it holds. As such, hiding and securing keys (as well as mitigating potential loss) is critical for any security plan in the modern API space.
Today, we’re going to look at this problem and identify the core concerns. We’ll discuss three general ways to secure API keys and deliver an actionable, easy, and efficient solution for practical implementation.
Defining the Problem
Third-party integrations, automation, and collaborative work have all brought many advantages to modern developers. That said, API-based services carry significant risks that were less common in the classical coding model. Chief of these concerns is the exposure of sensitive information due to simple human error.
While many data types can damage your security if exposed, API key disclosure is especially worrisome due to the power they hold. When using something like GitHub, a significant concern is the exposure of API keys used during development. GitHub synchronization necessarily includes a lot of data, and if misconfigured, could also include your API key.
Why Should API Keys be Obfuscated and Secured?
First thing’s first – API keys are a common method of controlling access to web services. During testing and development, it’s common for developers to test keys and admin credentials within prototypes. The problem comes when developers do not treat this key with the security mindset that it demands.
If an admin or developer pushes their key as part of a commit accidentally, anyone who grabs that key can essentially act as if they were that person. The damage might not be immediate – in fact, it may not be immediately apparent that the key was compromised – but the potential for damage is extreme.
Dealing with the Problem
When we look at how we might solve this problem, there are essentially three categories of fixes. One should note that no single solution will cover every single use case. All of the solutions should be considered tools within a greater toolset, not a singular solution.
- Hiding Keys: The first type of solution involves different techniques for hiding your keys. Hiding your keys is an ok solution – after all, making sure your key doesn’t get exposed in the first place is about as effective a preventative measure as you could want.
- Securing Keys: Due to this fundamental flaw, we should consider securing our keys. Securing keys calls upon a varied set of solutions that both protect the keys themselves as well as securing the underlying system.
- Mitigation: Finally, we can put into place solutions that mitigate whatever potential damage that an exposed key might deal with. This is one part proactive, and one part reactive. Certain options can only be taken after the attack has started, but there are strategies to ensure damage happens in a known, predicted, and channeled manner.
Solutions for Hiding Keys
First and foremost, ensuring proper versioning processes is chief in hiding your key. Too often, code pushes do not undergo an appropriate process of review. When versioning, the codebase should be checked, reviewed, and authorized for publication. Standardized routines reduce the risk of key exposure.
Another good option to hide your keys is to move those keys outside of the source file structure. Instead, reference it as a variable. An environmental variable makes it far easier to reference said key in multiple locations, saving time, and increasing security.
Solutions for Securing Keys
Hiding keys is only part of the solution. So, what can you do in terms of securing the keys themselves? First, consider hashing and encrypting your key both at rest and in transit. Correctly done, this should add very little overhead to your interaction times, but ensures that any man-in-the-middle attacks or other breaches are difficult to leverage into greater damages immediately.
Secondly, routinely audit your keys to make sure that there are no extra unused keys or over-privileged keys waiting to make a disaster of your codebase. Excluding old keys from use by enforcing both an automatic key regeneration schedule and a key expiration mechanism can ensure that even if keys are accidentally exposed, the time during which they can do damage is at least partially limited.
One major practice to adopt as a general guideline is an idea that roles – and thereby, keys – should be partitioned and sensibly according to the least privilege needed to carry out the essential function. In other words, there’s really no reason there should be an omnibus key that does everything everywhere – such a key would be disastrous if exposed.
Finally, ensure that all the systems in place have methods to detect changes in activity on a key-to-key basis. If your key interacts with the API predictably, you can ensure that any change in that activity can, at the very least, be flagged for review, if not immediately result in the revocation of rights for that key. Such a system is principally reactive, but it can do wonders to stop the attack as it happens.
Solutions for Mitigating Exposure
Planning for the worst scenario is perhaps your best strategy. First, developers should apply key restrictions sensibly. Allowing only specific clients or specific aspects of the API to interact with a key limits the damage that key can do, and as previously stated, there’s no need to have an omnibus key that is used continuously for a single function.
Another solution may be tying particularly powerful keys to IPs or authentication methods. Requiring bulk efforts to have a second factor before the call is authorized, tying admin keys to admin IP addresses, etc. can limit key damages significantly.
Finally, limiting the number of actions a key can enact independently can be a major blocker for certain types of attacks. Having a hard limit to what can be done in a short amount of time negates many flooding attacks, and can, therefore, limit both data exfiltration, abusive API uses, and concurrent connections.
Let’s take a look at a practical option to see what such a cohesive approach might look like. Once we’ve ensured that our key expiration and role separation is appropriate, we can begin to look towards the more technical implementation.
This implementation primarily uses dotenv. It begins with a pretty sensible separation of the implementation itself and the fundamental way in which we interface with our collaborative solution, GitHub. First, we need to create a
.gitignore file that specifically points to files that we want GitHub syncs to ignore. As we are creating a
.env file, we will list that preemptively as excluded.
Now that we have the foundations for what we need to do, we can start. First, install
dotenv (in this case, with NPM):
npm install dotenv
Now let’s create the actual
.env file. We can do this by using the
!touch command, which will create a new blank folder in the same directory as our API codebase:
Within this file, we should insert our API key. We can do this by stating
PROJECT_API_KEY=, and inserting the key as the variable. For instance, ours might look as follows:
Now that we have the variable set, we can utilize
load_dotenv to load the variable. This looks like:
from dotenv import load_dotenv
API_KEY = os.getenv('PROJECT_API_KEY')
What this will ultimately look like in terms of practical use differs depending on which side of the API codebase you’re looking at. As a user, you’ll only see the
PROJECT_API_KEY variable. The key itself will exist outside of this commit data and will be obfuscated. For developers and internal staff who have access to the project root, they’ll be able to see both the .env file and the committed codebase. As such, those who need to utilize the
.env file will be able to.
The accidental synchronization of secure code is not uncommon. The solutions to hide, secure, and mitigate are thankfully very easy to implement and can be done broadly across almost any current implementation. Ensuring the security of your API key should be a major focus for any organization, and is a first major step for any security plan.
What do you think about this implementation? Is the danger of API key exposure overblown? Let us know in the comments below.