Add \ignorespaces in the appropriate places.
\documentclass{article}
\usepackage{etoolbox}
%---------------------------------------------------------------------
% my cls files
\providetoggle{firstkey}
\settoggle{firstkey}{true}
\newcommand{\keys}[1]{\newcommand{\mykeys}{\ignorespaces#1.}}
\newcommand{\key}[1]{%
\iftoggle{firstkey}{#1}{; #1}%
\settoggle{firstkey}{false}%
\ignorespaces
}
%-----------------------------------------------------------------------
\keys{
\key{aaaaa}
\key{bbbbb}
\key{ccccc}
}
\begin{document}
X\mykeys X
\end{document}
The X characters are used to show that no spurious space gets inserted.

A different approach with expl3. The user inputs the keys with a more natural syntax and it's a job for expl3 to normalize it. In the example there is a trailing semicolon that would produce an empty key, but it can be easily checked for and removed.
\documentclass{article}
\usepackage{xparse}
%---------------------------------------------------------------------
% my cls files
\ExplSyntaxOn
\NewDocumentCommand{\keys}{m}
{
\seq_set_split:Nnn \l_tmpa_seq { ; } { #1 }
\tl_if_blank:xT { \seq_item:Nn \l_tmpa_seq { -1 } }
{% user has a trailing semicolon, remove the last (empty) item
\seq_pop_right:NN \l_tmpa_seq \l_tmpa_tl
}
\tl_set:Nx \mykeys { \seq_use:Nn \l_tmpa_seq { ;~ } .}
}
\prg_generate_conditional_variant:Nnn \tl_if_blank:n { x } { T }
\ExplSyntaxOff
%-----------------------------------------------------------------------
\keys{
aaaaa;
bbbbb;
ccccc;
}
\begin{document}
X\mykeys X
\end{document}